Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6428,3 +6428,17 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)


450. **`prompt` emits `kind:"missing_credentials"` JSON on STDERR (not stdout), leaving stdout at 0 bytes — automation pattern `output=$(claw prompt hello --output-format json)` captures nothing on auth-absent failure; `doctor` correctly surfaces `auth.status:"warn"` with `api_key_present:false` but exposes no `prompt_ready:false` field that automation can check before invoking `prompt`** — dogfooded 2026-05-16 by Jobdori on `a35ee9a0` in response to Clawhip pinpoint nudge at `1505208225321062521`. Exact reproduction (isolated env, no creds, fresh git repo, HEAD `a35ee9a0`): `timeout 5 env -i HOME=$ISOLATED_HOME PATH=$PATH CLAW_CONFIG_HOME=$PROBE/.claw-cfg claw prompt hello --output-format json > stdout.txt 2> stderr.txt` → stdout = **0 bytes**, stderr = 195 bytes containing `{"error":"missing Anthropic credentials…","exit_code":1,"hint":null,"kind":"missing_credentials","type":"error"}`, exit code 1. Confirms Gaebal's `1505208553793781792` pinpoint that `prompt` timeout + zero bytes was the prior state — HEAD `a35ee9a0` now correctly exits 1 with `kind:"missing_credentials"` **but the envelope is still routed to stderr** (issue #447 class, same class as prior entries #422, #435). **Contrast with `doctor`:** `claw doctor --output-format json 2>/dev/null` succeeds to stdout with `checks[auth].status:"warn"`, `api_key_present:false`, `auth_token_present:false` — but the auth check has no `prompt_ready:false` field. Automation that gates on `doctor` before invoking `prompt` must re-derive readiness from `api_key_present && auth_token_present` — there is no single canonical boolean. **Three compound problems:** (a) **stdout-empty on `--output-format json` failure**: same class as #447; `prompt`'s error envelope goes to stderr, not stdout. The canonical automation idiom `if ! result=$(claw prompt "q" --output-format json); then echo "$result" | jq .kind; fi` sees `$result=""` on failure — the jq call gets nothing. All `--output-format json` error paths must route JSON to stdout per #447 contract; (b) **`doctor` missing `prompt_ready` field**: `doctor --output-format json` already knows auth is absent (`api_key_present:false`) but surfaces no derived `prompt_ready:bool` or `prompt_blocked_reason:string` field. Automation must infer readiness from `api_key_present || auth_token_present || legacy_*_present` — a 5-field OR across legacy fields that is fragile as auth mechanisms evolve. A single `prompt_ready:false` (with `prompt_blocked_reason:"auth_missing"`) inside the `auth` check would give downstream a stable contract; (c) **`claw prompt` with no auth does no preflight and fires straight at the API**: the preflight check that `doctor` runs (auth discovery) is not reused by `prompt` to emit a fast typed error before attempting the network call. Both Gaebal's pinpoint (prompt hanging silently on older HEAD) and the current behavior (prompt hitting auth gate after a brief API attempt) stem from the same root: prompt does not short-circuit at the point where `doctor` already knows auth is absent. If `doctor` can emit `kind:"doctor"` with `auth.status:"warn"` in ~20ms without a network call, `prompt` should emit `kind:"missing_credentials"` in the same window and output it to stdout. **Required fix shape:** (a) `prompt --output-format json` must write the `kind:"missing_credentials"` JSON envelope to **stdout**, not stderr — same fix as #447 for all error envelopes; (b) add `prompt_ready:bool` and `prompt_blocked_reason:string|null` to the `auth` check in `doctor --output-format json`; derive it as `api_key_present || auth_token_present || legacy_saved_oauth_present`; (c) `prompt` must run the credential preflight check (same codepath as doctor's auth check) before attempting any API call and emit `{"kind":"missing_credentials","prompt_blocked_reason":"auth_missing"}` on **stdout** with exit 1 if the check fails; (d) `--output-format json` stdout routing fix must cover: `prompt`, `session list` (cross-ref #449), `skills uninstall` (cross-ref #431), `resume` (cross-ref #435), `acp serve` (cross-ref #443) — the full `kind:"missing_credentials"` class; (e) regression test: `claw prompt hello --output-format json` with no creds writes JSON to stdout (0 bytes stderr), exits 1, `kind:"missing_credentials"`, in under 200ms (no network attempt). **Why this matters:** `prompt` is the primary consumer entry point. Auth-absent failure routing to stderr breaks every automation wrapper that captures `$(claw prompt ... --output-format json)`. The `doctor` preflight metadata gap means auth-readiness checks require parsing 5 legacy fields instead of reading one boolean. Cross-references #447 (all JSON error envelopes on stderr), #449 (session list hits auth gate), #431 (skills uninstall hits auth gate), #357 (auth gate on local ops cluster), #422 (exit-code parity). Source: Jobdori live dogfood, `a35ee9a0`, 2026-05-16.

457. **`claw --resume --help` and `claw --resume --version` disagree by parser asymmetry: `--version` has no rest-emptiness guard so it short-circuits before resume dispatch (prints version, exit 0), but `--help` is guarded by `rest.is_empty()` plus a hardcoded 4-subcommand allowlist, so after `--resume` is appended to `rest` the `--help` token falls through to the catch-all and is consumed as the session-id literal — `failed to restore session: session not found: --help`, exit 1. Help is inaccessible from any `claw --resume …` invocation, while version is freely accessible from the same invocation. Sibling: every other discovery verb the user would intuit (`claw --resume help`, `claw --resume list`, `claw --resume ls`, `claw --resume show`) lands in the same trap, and the hint text in those error messages directs users to `/session list` which only works in the REPL** — dogfooded 2026-05-24 for the 08:00 Clawhip pinpoint nudge at message `1508016732986408983`, reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`). Clean repro in an isolated environment (`HOME=/tmp/iso7/home` with no claw config, fresh `/tmp/iso7/proj` git-init'd workspace, separated stdout/stderr/exit):

| Invocation | stdout | stderr | exit |
|---|---|---|---|
| `claw --resume --version` | (version banner) | _empty_ | **0** |
| `claw --resume --help` | _empty_ | `failed to restore session: session not found: --help…` | **1** |
| `claw --resume version` | _empty_ | `failed to restore session: session not found: version…` | 1 |
| `claw --resume help` | _empty_ | `failed to restore session: session not found: help…` | 1 |
| `claw --resume list` | _empty_ | `failed to restore session: session not found: list…` | 1 |
| `claw --resume ls` | _empty_ | `failed to restore session: session not found: ls…` | 1 |
| `claw --resume show` | _empty_ | `failed to restore session: session not found: show…` | 1 |

Same parser, same `--resume <ARG>` shape, **only `--version` short-circuits**. **Root cause (traced):** `parse_args` in `rust/crates/rusty-claude-cli/src/main.rs:630-654` has asymmetric `--help`/`--version` arms. `"--help" | "-h" if rest.is_empty() => { wants_help = true; index += 1; }` at line 632, plus a second `--help` arm at lines 636-650 gated by `if !rest.is_empty() && matches!(rest[0].as_str(), "prompt" | "commit" | "pr" | "issue")` — both guards exclude `rest[0] == "--resume"`. Meanwhile `"--version" | "-V" => { wants_version = true; index += 1; }` at lines 651-654 has **no guard at all** — version always captures regardless of `rest` state. The `--resume` arm at line 762 (`"--resume" if rest.is_empty() => { rest.push("--resume".to_string()); index += 1; }`) pushes `"--resume"` into `rest` and advances by ONE, so the parser continues. On the next iteration with `rest = ["--resume"]`: `--help` fails both guards → falls through to the catch-all `other => { rest.push(other.to_string()); index += 1; }` at lines 793-796 → `rest = ["--resume", "--help"]` → resume dispatch later treats `--help` as the session-id positional argument → `session not found: --help` → exit 1. `--version` instead matches its unguarded arm → `wants_version = true` → the final `if wants_version { return Ok(CliAction::Version { ... }); }` at lines 803-805 short-circuits before resume dispatch ever runs. **Why distinct from existing items:** #117 (lines 3911-3913 in ROADMAP) covers `claw -p "test" --help` → `-p` swallows everything via greedy `args[index + 1..].join(" ")` and a hardcoded `return` from inside `-p`'s arm; that's a `-p`-specific greedy-swallow bug. This pinpoint is the **opposite asymmetry**: `--resume` is *not* greedy — it correctly advances by one and lets the parser continue — but the `--help` arm's `rest.is_empty()` guard plus the 4-name allowlist (`prompt|commit|pr|issue`) excludes `--resume` from the allowlist while `--version` has no such guard. The result: under `--resume`, version still works but help is unreachable. #2186 (existing `--help` Resume-safe block) tracks the inverse — `claw --help` itself lying about which resume-safe commands work — not "you cannot get to `--help` from a `--resume` line at all." #21/#55/#113 cover REPL-only session verbs (`/session list`/`/session switch`/etc.), not the CLI-side `--help` accessibility on a `--resume` line. #117's fix is "make `-p` non-greedy"; this fix is "give `--help` the same unguarded short-circuit as `--version`, OR add `--resume` to the help allowlist." **Why it matters:** (a) **Help discoverability is broken for the most common entry point.** A user typing `claw --resume <tab><tab>` is exploring; the next muscle-memory step is `claw --resume --help` to see "what's the resume subcommand syntax?" Instead they get `session not found: --help` with no indication that `--help` is a flag, not a session id. The hint text even directs them to `/session list` which they have no way to invoke from the same `--help`-blocked CLI. (b) **CLI/version parity contract violated.** Every other `claw <verb> --version` and `claw <verb> --help` pair returns the corresponding help/version page. `--resume` uniquely breaks the `--help` half of that contract while preserving the `--version` half — there's no documentation anywhere that `--resume` swallows `--help`. (c) **The "session not found: --help" message is actively misleading.** It implies the user provided a malformed session id, not that they tried to read documentation. A claw orchestrator that parses this error envelope (`kind:"session_not_found"`, `error:"…session not found: --help…"`) will not realize the human typed `--help`. (d) **Sibling clawability gap.** The same arm-structure means `help`, `list`, `ls`, `show` are all silently absorbed as session-id literals. There is no CLI way to enumerate sessions — `/session list` (REPL-only, called out in the hint!) is the only path. A non-interactive claw cannot ask "what sessions exist?" without either parsing `.claw/sessions/<fingerprint>/` directly (bypasses claw bookkeeping) or spawning a TTY (#113 already covers the broader REPL-only session-verb gap, but this pinpoint shows the CLI-side hint message itself is recommending a command the CLI cannot run). **Required fix shape:** (a) **make `--help` short-circuit unconditionally**, matching `--version` — remove the `if rest.is_empty()` and the 4-name allowlist guards on the top-level `--help` arms in `parse_args`; let every `--help` anywhere in argv return `CliAction::Help { output_format }`. The reason for the original guards (per the comment at lines 638-647: "Subcommands that consume their own args (agents, mcp, plugins, skills) and local help-topic subcommands … must NOT be intercepted here — they handle --help in their own dispatch paths via parse_local_help_action()") is to let subcommand-local help win over global help. But `--resume` is **not** a subcommand-local-help path — it's a top-level flag whose `<ARG>` consumes positional input, and its dispatch has no `parse_local_help_action()` analogue. Either add `--resume` to the allowlist `matches!(rest[0].as_str(), "prompt" | "commit" | "pr" | "issue" | "--resume")` so `--help` after `--resume` triggers top-level help, OR (simpler) drop the global `--help` guards entirely and let any `parse_local_help_action()` path do its own dispatch before `parse_args` runs. (b) **CLI-side session enumeration.** Add a `claw session list` / `claw sessions` top-level subcommand (or an explicit `claw --list-sessions` flag) that emits the same `{kind:"session_list", sessions:[…], active:<id>}` JSON envelope as the REPL `/session list`, so the hint text in every session-not-found error has an actually-callable CLI parallel. Sibling of #113's broader session-verb gap but distinct: this is the minimum-viable list capability, not the full switch/fork/delete matrix. (c) **Better error wording when the "session id" looks like a flag.** When the resume-dispatch session-id resolver receives a string that starts with `-` (e.g. `--help`, `-h`, `--version`, `--output-format`), emit `kind:"flag_swallowed_as_session_id"` with `flag:"--help"`, `hint:"--help must appear before --resume or use claw --help on its own; --resume <ID> takes a session id literal, not a flag"`. Same for `latest`-adjacent typos: `list`, `ls`, `show`, `help` could carry `hint:"did you mean to call /session list? CLI equivalent is claw session list"`. (d) **Regression coverage.** Property test asserting `claw --resume --help`, `claw --resume -h`, `claw --resume --help --output-format json`, `claw --resume foo --help`, and `claw --help --resume foo` ALL produce `CliAction::Help { … }` exit 0; assert `claw --resume help`, `claw --resume list`, `claw --resume ls`, `claw --resume show` produce a structured `flag_swallowed_as_session_id` or `unsupported_session_alias` envelope with `kind` set, never the catch-all `session_not_found` bucket. **Acceptance check (one-liner):** `claw --resume --help >/dev/null 2>&1; test $? -eq 0` should pass (and stdout should contain the resume help text). Source: gaebal-gajae dogfood follow-up for the 2026-05-24 08:00 Clawhip pinpoint nudge at message `1508016732986408983`.
Loading