diff --git a/ROADMAP.md b/ROADMAP.md index 8111885d..ced054e3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4342,7 +4342,7 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label], **Source.** Jobdori dogfood 2026-04-18 against `/tmp/cdWW` on main HEAD `b81e642` in response to Clawhip pinpoint nudge at `1494963222157983774`. Joins **Claude Code migration parity** (#103, #109, #116, #117, #119, #120) as 7th member — the most severe parity break since hooks is load-bearing automation infrastructure. Joins **Truth-audit / diagnostic-integrity** on misleading error message axis. Joins **Silent-flag / documented-but-unenforced** on NDJSON-output-violating-json-flag. Cross-cluster with **Unplumbed-subsystem** (#78, #96, #100, #102, #103, #107, #109, #111, #113) — hooks subsystem exists but schema is incompatible with the reference implementation. Natural bundle: **Claude Code migration parity septet (grown)**: #103 + #109 + #116 + #117 + #119 + #120 + **#121**. Complete coverage of every migration failure mode: silent drop (#103) + stderr prose warnings (#109) + hard-fail on unknown keys (#116) + prompt corruption from muscle memory (#117) + slash-verb fallthrough (#119) + JSON5-partial-accept + alias-inversion (#120) + hooks-schema-incompatible (#121). Also **#107 + #121** — hooks-subsystem pair: #107 hooks invisible to JSON diagnostics + #121 hooks schema incompatible with migration source. Also **NDJSON-violates-json-flag 2-way (new)**: #121 + probably more; worth sweep. Session tally: ROADMAP #121. -122. **`--base-commit` accepts ANY string as its value with zero validation — no SHA-format check, no `git cat-file -e` probe, no rejection of values that start with `--` or match known subcommand names. The parser at `main.rs:487` greedily takes `args[index+1]` no matter what. So `claw --base-commit doctor` silently uses the literal string `"doctor"` as the base commit, absorbs the subcommand, falls through to Prompt dispatch, emits stderr `"warning: worktree HEAD (...) does not match expected base commit (doctor). Session may run against a stale codebase."` (using the bogus value verbatim), AND burns billable LLM tokens on an empty prompt. Similarly `claw --base-commit --model sonnet status` takes `--model` as the base-commit value, swallowing the model flag. Separately: the stale-base check runs ONLY on the Prompt path; `claw --output-format json --base-commit status` or `doctor` emit NO stale_base field in the JSON surface, silently dropping the signal (plumbing gap adjacent to #100)** — dogfooded 2026-04-18 on main HEAD `d1608ae` from `/tmp/cdYY`. +122. **DONE — `--base-commit` accepts ANY string as its value with zero validation — no SHA-format check, no `git cat-file -e` probe, no rejection of values that start with `--` or match known subcommand names. The parser at `main.rs:487` greedily takes `args[index+1]` no matter what. So `claw --base-commit doctor` silently uses the literal string `"doctor"` as the base commit, absorbs the subcommand, falls through to Prompt dispatch, emits stderr `"warning: worktree HEAD (...) does not match expected base commit (doctor). Session may run against a stale codebase."` (using the bogus value verbatim), AND burns billable LLM tokens on an empty prompt. Similarly `claw --base-commit --model sonnet status` takes `--model` as the base-commit value, swallowing the model flag. Separately: the stale-base check runs ONLY on the Prompt path; `claw --output-format json --base-commit status` or `doctor` emit NO stale_base field in the JSON surface, silently dropping the signal (plumbing gap adjacent to #100)** — dogfooded 2026-04-18 on main HEAD `d1608ae` from `/tmp/cdYY`. **Concrete repro.** ``` diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 260242cc..9c94bb97 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1598,6 +1598,16 @@ fn parse_args(args: &[String]) -> Result { let value = args .get(index + 1) .ok_or_else(|| "missing_flag_value: missing value for --base-commit.\nUsage: --base-commit ".to_string())?; + // #122: validate that base-commit looks like a git SHA (hex, 7-64 chars) + if value.len() < 7 + || value.len() > 64 + || !value.chars().all(|c| c.is_ascii_hexdigit()) + { + return Err(format!( + "invalid_flag_value: --base-commit expects a hex SHA (7-64 chars), got '{}'.\nUsage: --base-commit ", + value + )); + } base_commit = Some(value.clone()); index += 2; }