Skip to content

fix(cli): stop --full-path from degrading silently (#785) - #786

Open
danmackinlay wants to merge 1 commit into
tobi:mainfrom
danmackinlay:fix/full-path-unresolved-signal
Open

fix(cli): stop --full-path from degrading silently (#785)#786
danmackinlay wants to merge 1 commit into
tobi:mainfrom
danmackinlay:fix/full-path-unresolved-signal

Conversation

@danmackinlay

Copy link
Copy Markdown
Contributor

Fixes #785. Independent of #784 — branched from main, no overlap beyond both adding a [Unreleased] changelog block.

--full-path exists to emit openable filesystem paths. Falling back to the qmd:// URI when a result can't be resolved is the right call, but search/query did it unannounced and dropped the docid, so the row ended up with neither a usable path nor an identifier field — and nothing distinguished it from a resolved row.

Since 070147d (literal paths) this no longer fires for normalization reasons. It fires when the file moved or was deleted since the last index, which is useful information that was being swallowed.

Before

$ qmd search beta --full-path --json      # beta.md was moved after indexing
[
  { "score": 0, "file": "./alpha.md",          "title": "Alpha" },
  { "score": 0, "file": "qmd://stale/beta.md", "title": "Beta"  }
]
$ echo $?
0

No docid on the second row, nothing on stderr. qmd get --full-path and multi-get --full-path already kept the docid — only outputResults() keyed the decision off the flag rather than off the row.

After

$ qmd search beta --full-path --json
[
  { "score": 0, "file": "./alpha.md",          "title": "Alpha" },
  { "docid": "#4cbea2", "score": 0, "file": "qmd://stale/beta.md", "title": "Beta" }
]
warning: --full-path could not resolve 1 of 2 results on disk (moved or deleted since indexing); showing qmd:// + docid instead. Run 'qmd update' to refresh the index.

The warning is on stderr, so stdout stays machine-readable and pipelines are unaffected.

Changes

  1. Per-row decision in search/query, matching multiGet(): resolved → on-disk path, no docid; unresolved → qmd:// URI with docid. Applies to cli, json, md, xml, csv. Rows are resolved up front so the count is known before anything prints.
  2. stderr notice from search, query, get and multi-get when any row falls back, pointing at qmd update.
  3. search --format csv always emits the docid column (empty when the row resolved), so columns stay positional. The --full-path header used to drop it — which also disagreed with printEmptySearchResults(), whose CSV header always carries docid, so an empty result set and a non-empty one printed different headers for the same command.

Two judgement calls worth a look

  • --files is left alone. Its --full-path shape stays score,path, so cut -d, -f2 | xargs keeps working; adding a leading docid field only for unresolved rows would break positional parsing, and adding it unconditionally would break every existing pipeline. Unresolved rows there show the qmd:// URI and are covered by the stderr notice. Happy to change it if you'd rather have consistency with CSV.
  • Exit code stays 0. A moved file is a stale index, not a failed search. The notice is the signal.

Tests

New test/full-path-fallback.test.ts (7 cases) over a corpus where one file is moved out from under the index: docid retention in --json, the CSV column layout, docid in the default CLI format, get and multi-get warnings, no warning when everything resolves, and no behavior change without --full-path. Existing --full-path tests in cli.test.ts (which assert resolved rows still drop the docid) pass unchanged.

Full suite: 954 passed / 7 skipped. mcp.test.ts > MCP HTTP Transport fails on my machine (SqliteError: no such column: T.name) identically on a clean main — environmental, unrelated.

`--full-path` exists to emit openable filesystem paths. When a result can't
be resolved on disk it falls back to the `qmd://` URI, which is the right
call — but search/query did it unannounced *and* dropped the docid, leaving
the row with neither a usable path nor an identifier field. Since 070147d
(literal paths) that fallback no longer fires for normalization reasons: it
means the file moved or was deleted since the last index, which is worth
saying out loud.

- search/query now decide per row, like multiGet() already did: resolved →
  on-disk path, no docid; unresolved → `qmd://` URI *with* the docid. Applies
  to the cli, json, md, xml and csv formats.
- search/query/get/multi-get print a notice to stderr naming how many results
  fell back and pointing at `qmd update`. stdout stays machine-readable.
- `search --format csv` always emits the `docid` column (empty when the row
  resolved) so column positions are stable. The `--full-path` header used to
  drop it, which also disagreed with printEmptySearchResults(), whose header
  always carries `docid`.

`--files` keeps its current two-column shape under `--full-path` so
`cut -d, -f2 | xargs` pipelines don't break; unresolved rows there show the
qmd:// URI and are covered by the stderr notice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PowderAddicts

PowderAddicts commented Jul 24, 2026 via email

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--full-path silently drops the docid and falls back to qmd:// when a result can't be resolved on disk

2 participants