Skip to content

cli: harden argument parsing, validation, and exit codes (#528)#529

Merged
justrach merged 1 commit into
release/0.2.5824from
fix/cli-parsing-528
Jun 4, 2026
Merged

cli: harden argument parsing, validation, and exit codes (#528)#529
justrach merged 1 commit into
release/0.2.5824from
fix/cli-parsing-528

Conversation

@justrach

@justrach justrach commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Fixes the CLI parsing / status / exit-code regressions reported in #528. Closes #528.

The root cause across most findings was ad-hoc, per-command positional parsing. This PR introduces small pure, unit-tested argument parsers (parseLineRange, parseSearchArgs, parseDepsArgs) and routes the bridged commands through a shared exit-code helper.

Findings addressed

# Issue Fix
1 no codedb status CLI command added a read-only status renderer mirroring codedb_status (version, root, files, seq, outlines, index)
2 deps --depends-on <path> misparsed flag as path parseDepsArgs: flags any order, first non-flag = path
3 malformed read -L nope treated as a path parseLineRange → clear invalid line range error, exit 1
4 reversed read -L 20-1 exited 0 with no output rejected as Reversed, exit 1
5 zero-result exit-code semantics undocumented kept exit 0 for valid-but-empty results; documented in --help
6 bridged handlers printed error: but exited 0 finishCli returns 1 when the handler's output starts with error: (same marker MCP uses for isError); zero-result wording keeps exit 0
7 read -L 0-3 / abc-10 / 10-abc silently defaulted parseLineRange rejects zero/non-numeric ends, exit 1
8 read <path> -L 1-3 ignored trailing flags flags now parsed before or after the path
9 search --max-results 1 foo searched literal --max-results parseSearchArgs: reject unknown flags, support --max-results N, reject empty query, -- ends flags
10 invalid regex swallowed → "no results" searchContentRegex[WithScope] pre-compiles and returns error.InvalidRegex; CLI/MCP report invalid regex
11 deps ignored unknown flags / coerced bad --max-depth to 1 reject unknown flags; require a positive integer depth
12 --no-telemetry documented global but only parsed after mcp stripped/honored in the global arg filter (like --config-file)

#13 (blanket arity validation) is partially covered: read/search/deps now reject unknown flags and stray extra args. A repo-wide arity sweep for every command was intentionally left out to avoid breaking callers that pass extra args.

Tests

  • src/test_mcp.zigparseLineRange, parseSearchArgs, parseDepsArgs, and finishCli (exit-code mapping incl. zero-result = exit 0).
  • src/test_explore.zigsearchContentRegex surfaces error.InvalidRegex.
  • zig build test is green.

End-to-end verification

Ran the binary against the issue's repro cases:

read -L nope src/main.zig        -> exit 1  ✗ invalid line range: nope
read -L 20-1 src/main.zig        -> exit 1  ✗ invalid line range: 20-1
read -L 0-3  src/main.zig        -> exit 1  ✗ invalid line range: 0-3
read src/main.zig -L 1-3         -> exit 0  (lines 1-3)        # flags after path
search --max-results 1 const     -> exit 0  1 results for "const"
search ''                        -> exit 1  usage
search --bogus const             -> exit 1  unknown flag
search --regex '['               -> exit 1  ✗ invalid regex: [
deps --depends-on src/main.zig   -> exit 0  src/main.zig depends on: std
deps src/main.zig --max-depth notnum -> exit 1
context ab                       -> exit 1  error: task must be 3-1024 chars
glob ''                          -> exit 1
status                           -> exit 0  codedb <ver> / root / files / seq / outlines / index
--no-telemetry <root> status     -> exit 0  (flag before AND after root)

🤖 Generated with Claude Code

Fixes the CLI parsing/status/exit-code regressions reported in #528.

- read -L: reject malformed / non-numeric / zero / reversed ranges with a
  clear error instead of silently defaulting (`abc-10` -> 1) or printing
  empty exit-0 output (`20-1`); parse `-L`/`--compact` before OR after the
  path, and reject unknown flags + extra args (items 3, 4, 7, 8)
- runCliTool: return exit 1 when a bridged handler emits an `error:` line
  (context / glob / file / symbol / deps were exit 0 on failure), keying off
  the same `error:` marker MCP uses; zero-result wording keeps exit 0 (item 6)
- deps: parse flags in any order, resolve the first non-flag token as the
  path, reject unknown flags, require a positive `--max-depth` (items 2, 11)
- search: reject unknown `--flags` instead of searching for the flag text,
  support `--max-results N`, reject empty query, and let `--` end flag
  parsing so a literal `--foo` can still be searched (item 9)
- regex: surface error.InvalidRegex instead of swallowing it and reporting
  "no results" (item 10)
- --no-telemetry: strip/honor it globally (was only parsed after `mcp`),
  matching its documented "global option" status (item 12)
- status: add a read-only `codedb status` CLI command mirroring codedb_status
  (item 1)
- docs: document zero-result = exit 0 semantics in `--help` (item 5)

New pure helpers parseLineRange / parseSearchArgs / parseDepsArgs replace the
ad-hoc positional parsing and are unit-tested in test_mcp.zig; the invalid-regex
path is tested in test_explore.zig. zig build test is green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Benchmark Regression Report

Thresholds: 10.00% and 50,000 ns absolute delta

NOISE means the percentage threshold was exceeded, but the absolute delta was too small to fail CI.

Tool Base (ns) Head (ns) Delta Abs Delta (ns) Status
codedb_bundle 64526 71440 +10.72% +6914 NOISE
codedb_changes 6450 6009 -6.84% -441 OK
codedb_context 1038096 1053587 +1.49% +15491 OK
codedb_deps 231 242 +4.76% +11 OK
codedb_edit 41038 39442 -3.89% -1596 OK
codedb_find 5011 5135 +2.47% +124 OK
codedb_hot 14003 14860 +6.12% +857 OK
codedb_outline 24821 26881 +8.30% +2060 OK
codedb_read 11458 15103 +31.81% +3645 NOISE
codedb_search 30610 24530 -19.86% -6080 OK
codedb_snapshot 62136 64416 +3.67% +2280 OK
codedb_status 4969 4857 -2.25% -112 OK
codedb_symbol 20489 21130 +3.13% +641 OK
codedb_tree 16554 25840 +56.10% +9286 NOISE
codedb_word 6740 6840 +1.48% +100 OK

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1cba0f601a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main.zig
Comment on lines +921 to +927
} else if (std.mem.eql(u8, a, "--no-telemetry")) {
// #528 item 12: --no-telemetry is documented as a global option,
// so strip it before positional parsing (like --config-file)
// instead of only honoring it after `mcp`. Telemetry.init also
// honors CODEDB_NO_TELEMETRY.
no_telemetry = true;
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve literal --no-telemetry command arguments

Because --no-telemetry is stripped before positional parsing regardless of command context, users can no longer pass it as data to commands that explicitly support -- for literals. For example, codedb search -- --no-telemetry is filtered down to search -- and then fails with MissingQuery, even though parseSearchArgs documents and tests -- as the escape for searching ---prefixed strings; this also prevents searching for this flag in the codebase itself. Consider only treating it as global before the command/root boundary or otherwise preserving it after a command's -- sentinel.

Useful? React with 👍 / 👎.

@justrach justrach merged commit 4f31e7e into release/0.2.5824 Jun 4, 2026
1 check passed
@justrach justrach deleted the fix/cli-parsing-528 branch June 4, 2026 03:14
justrach added a commit that referenced this pull request Jun 4, 2026
…, CLI hardening, ReScript

- release_info.semver 0.2.5823 -> 0.2.5824 (the version codedb reports and
  update.zig compares against; build.zig.zon was already 0.2.5824).
- CHANGELOG: document the warm CLI daemon (#525), faster fuzzy find (#526),
  CLI hardening (#529), ReScript .res/.resi (#532), and audit fixes (#530)
  alongside the existing code-graph / snapshot-load sections.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant