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
2 changes: 2 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6428,3 +6428,5 @@ 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.

456. **`claw doctor` reports the same fact ("how many config files were discovered") under two semantically-identical JSON keys with *different definitions and different counts* — `config.discovered_files_count` filters to paths that exist on disk, while `workspace.discovered_config_files` returns the raw candidate-search-path list including paths that do not exist, so the same envelope contradicts itself** — dogfooded 2026-05-24 for the 07:30 Clawhip pinpoint nudge at message `1508009183260442777`, reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`). Repro in a clean isolated environment (`HOME=/tmp/iso6/home` with no `.claw.json`, fresh `/tmp/iso6/proj` git-init'd workspace): `claw doctor --output-format json` returns `config.discovered_files_count = 0`, `config.discovered_files = []`, summary `"no config files present; defaults are active"`, **and at the same time** `workspace.discovered_config_files = 5`, summary `"project root detected on branch master"`. In the real repo where one `.claw.json` exists, the same command returns `config.discovered_files_count = 1` and `workspace.discovered_config_files = 5`. The human-facing text envelope leaks the same contradiction: the Config section says `Config files loaded 0/0` and `Discovered files <none> (defaults active)`, while the Workspace section says `Memory files 0 · config files loaded 0/5` — three different values for the same fact in one report. **Root cause (traced):** the `config` check (`rust/crates/rusty-claude-cli/src/main.rs:2180-2202`) does `let discovered = config_loader.discover();` then `let present_paths = discovered.iter().filter(|e| e.path.exists()).collect();` and emits `discovered_files_count = present_paths.len()`, deliberately hiding non-existent candidate paths (a comment at lines 2183-2186 says `"Showing non-existent paths as 'Discovered file' implies they loaded but something went wrong, which is confusing. We only surface paths that exist on disk as discovered; non-existent ones are silently omitted from the display"`). The `workspace`/`status_context` builder (`rust/crates/rusty-claude-cli/src/main.rs:5759-5764`) does `let discovered_config_files = loader.discover().len();` — **same `discover()` API, no `.exists()` filter** — and emits that raw candidate count as `workspace.discovered_config_files`. Both numbers flow into the same JSON envelope under near-identical key names. **Why distinct from existing items:** #143 (degrade-not-hard-fail on config parse failure) covers transport behaviour; #322/#447/#450 cover stderr-vs-stdout transport; #340 covers `type`/`kind` vocabulary; #449/#454/#451/#452/#453/#455 cover prompt-misdelivery and `missing_credentials` envelope shape. This pinpoint is **internal envelope self-consistency**: two checks in the same `doctor` invocation publish two different numbers for the same concept ("how many config files were discovered") under semantically-identical keys, using opposite definitions of the same `discover()` API. **Why it matters:** `doctor` is the structured health surface other claws/scripts/UI panels read to decide whether a workspace is "ready". A script that branches on `workspace.discovered_config_files > 0` (because that key name is the most obvious) will believe the workspace has 5 config files when it actually has 0 — false positive on "configured workspace" detection. Conversely, a script that branches on `config.discovered_files_count` correctly sees 0 — so two equally reasonable claws produce opposite decisions reading the same envelope. The contradiction also undermines `doctor` as a debugging tool: humans see `Discovered files <none>` and `config files loaded 0/5` in the same report and lose trust in every number it prints. **Required fix shape:** (a) pick **one** definition of "discovered config files" — strongly prefer "paths that exist on disk" (the user-meaningful number), since "candidate search paths" is an implementation detail of the loader; (b) rename the loader's raw candidate count to something explicit like `workspace.config_search_paths_count` and keep `discovered_config_files` aligned with `config.discovered_files_count`; (c) consolidate both checks to read the same `present_paths` computation rather than calling `discover()` twice with different filters — single source of truth; (d) regression coverage that the two values are equal across (i) empty workspace, (ii) workspace with one config file, (iii) workspace with a malformed config file (parse-failure path), and (iv) a parity test asserting `doctor` JSON has no two keys reporting different counts for the same concept; (e) fix the human text section so `Config files loaded N/M` uses the same `M` in both the Config and Workspace sections. **Acceptance check (one-liner):** `claw doctor --output-format json | jq -e '([.checks[] | select(.name=="config")][0].discovered_files_count) == ([.checks[] | select(.name=="workspace")][0].discovered_config_files)'` should pass on any workspace. Source: gaebal-gajae dogfood follow-up for the 2026-05-24 07:30 Clawhip pinpoint nudge at message `1508009183260442777`.
Loading