Skip to content

CLI arg parser: --limit / --snippet-lines validation errors are non-fatal — "Error: ..." prints but command continues with default, exit=0 (and --db=value / unknown flags have similar silent-fail shapes) #184

Description

@Widthdom

Summary

cdidx's CLI argument validation for numeric flags (--limit, --snippet-lines, possibly others) is non-fatal: the command prints Error: --limit requires a positive integer, got 'X', but then silently falls back to the default value (20 for --limit) and produces normal output with exit code 0 (success).

AI consumers running --count preflight ("is it safe to fetch all results?") get:

  • an Error: line mixed in with the numeric result on stdout/stderr
  • the reported count is the default-limit-capped number, not the user-requested count
  • exit code = 0 (success) — scripts branching on exit code trust the output

This is the same trust-signal-lies family as #159 (--count silently capped at --limit) but for invalid --limit values specifically.

Repro

curl -fsSL https://raw.githubusercontent.com/Widthdom/CodeIndex/main/install.sh | bash
CDIDX=/root/.local/bin/cdidx

# Index any project so there's something to search
git clone https://github.com/tokio-rs/tokio /tmp/tokio
"$CDIDX" /tmp/tokio --db /tmp/tokio.db

Smoking gun — --limit -1 --count

OUT=$("$CDIDX" search spawn --db /tmp/tokio.db --limit -1 --count 2>&1); EC=$?
echo "OUTPUT:"; echo "$OUT"; echo "EXIT=$EC"

Observed:

OUTPUT:
Error: --limit requires a positive integer, got '-1'
14
EXIT=0

The command prints an Error: line and a number (14), exits with 0. The 14 is the count with the DEFAULT --limit=20, NOT the user's -1. AI agents that parse the last line of stdout treat 14 as the answer.

Same shape reproduces for other numeric flags / other invalid values

# --limit abc (non-numeric)
"$CDIDX" search spawn --db /tmp/tokio.db --limit abc 2>&1 | head -2; echo exit=$?
# Error: --limit requires a positive integer, got 'abc'
# tokio/src/task/spawn.rs:141-215   ← full result set still printed
# exit=0

# --limit 0
"$CDIDX" search spawn --db /tmp/tokio.db --limit 0; echo exit=$?
# Error: --limit requires a positive integer, got '0'
# ...results...
# exit=0

# --limit 99999999999999 (overflow)
"$CDIDX" search spawn --db /tmp/tokio.db --limit 99999999999999; echo exit=$?
# Error: --limit requires a positive integer, got '99999999999999'
# ...results...
# exit=0

# --snippet-lines -1
"$CDIDX" search spawn --db /tmp/tokio.db --snippet-lines -1; echo exit=$?
# Error: --snippet-lines requires a positive integer, got '-1'
# ...results...
# exit=0

All five variants print an error message and still produce successful output with exit code 0.

Related arg-parser edge cases (same session)

  1. --db=value (equal-sign form) is silently rejected.

    $CDIDX status --db=/tmp/tokio.db; echo exit=$?
    # Warning: unknown option '--db=/tmp/tokio.db' (ignored)
    # Error: database not found at /home/user/CodeIndex/.cdidx/codeindex.db   ← defaulted
    # exit=3

    Users coming from git / gcc / dotnet conventions where --flag=value and --flag value are interchangeable hit this. The resulting "database not found" error is confusing — the user did supply a --db, cdidx just didn't parse it.

  2. Unknown flags warn, don't error.

    $CDIDX search spawn --db /tmp/tokio.db --nonexistent-flag foo; echo exit=$?
    # Warning: unknown option '--nonexistent-flag' (ignored)
    # ...normal search results...
    # exit=0

    Typos and renamed flags in user scripts become silent no-ops.

  3. --limit with a flag-shaped value (e.g. user forgot the value) consumes the next flag as the "value" and errors:

    $CDIDX search spawn --db /tmp/tokio.db --limit --lang rust; echo exit=$?
    # Error: --limit requires a positive integer, got '--lang'
    # ...search results (default limit)...
    # exit=0

    The --lang rust was intended as a separate filter but got consumed by --limit, and rust is left dangling. Again: error + success output + exit 0.

  4. --limit at end of args (no value follows) emits "unknown option '--limit'" — wrong wording (it's a known option, just missing its value).

  5. Duplicate --db /A.db --db /B.db silently uses the second without warning.

Suspected root cause

Likely in src/CodeIndex/Cli/ArgHelper.cs / QueryCommandRunner.cs: the numeric parser writes to stderr but returns a fallback default rather than propagating a failure signal. The command continues.

Non-fatal validation is often intentional for warnings, but for a typed numeric argument it should be an error: the user asked for something specific, the tool didn't honor it, the output doesn't match the request, yet exit is success.

Why it matters

Suggested direction

  1. Return CommandExitCodes.UsageError (1) on any numeric-arg validation failure. Don't print results after an error line; either honor the default with a warning (and a different exit) or refuse to run.

  2. Accept --flag=value form alongside --flag value. The parser can split on the first = when the flag is known to take a value.

  3. Treat unknown flags as errors by default. Keep an opt-in --ignore-unknown-options if there's a real use case, but the default should fail fast so typos surface immediately.

  4. "known option, missing value" is distinct from "unknown option" — say so. --limit at end of args should error with "--limit requires a value" not "unknown option '--limit'".

  5. Warn on duplicate value-taking flags. At minimum emit a note: Warning: --db specified twice; using '/tmp/B.db'.

Scope

  • src/CodeIndex/Cli/ArgHelper.cs — core parser. Add exit-on-validation-failure for numeric args; accept = form.
  • src/CodeIndex/Cli/QueryCommandRunner.cs — downstream handling.
  • tests/CodeIndex.Tests/QueryCommandRunnerTests.cs — fixtures for:
    • --limit -1 → exit 1, no result output
    • --limit abc → exit 1
    • --db=path → parsed the same as --db path
    • --unknown-flag foo → exit 1 (or opt-in to warn-only)
    • Duplicate --db → warn + use last (or error)

Related

Environment

  • cdidx: v1.10.0 (installed via install.sh).
  • Any indexed project reproduces. tokio-rs/tokio@master used as the query target above.
  • Platform: linux-x64 container.
  • Filed from a cloud Claude Code session per CLOUD_BOOTSTRAP_PROMPT.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions