From 4708ab16114fa0b507dc2aa2af74495ecae92588 Mon Sep 17 00:00:00 2001 From: bellman Date: Fri, 5 Jun 2026 04:14:08 +0900 Subject: [PATCH] fix: add structured help JSON and provider BASE_URL validation #683-#692: help topic JSON now includes usage, purpose, formats, related, local_only, requires_credentials, and aliases extracted from help prose. Export and doctor keep their custom structured responses. #466: new check_base_url_health() validates ANTHROPIC_BASE_URL, OPENAI_BASE_URL, XAI_BASE_URL, and DASHSCOPE_BASE_URL for basic HTTP(S) URL format. Non-http schemes and empty values produce a warn-level diagnostic. Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code --- ROADMAP.md | 24 +-- rust/crates/rusty-claude-cli/src/main.rs | 141 +++++++++++++++++- .../tests/output_format_contract.rs | 3 +- 3 files changed, 153 insertions(+), 15 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f5a8981e..633f564e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6428,7 +6428,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 450. **DONE — doctor auth check now includes `prompt_ready` field** — fixed 2026-06-04 in `fix: add prompt_ready to doctor auth check`. The `check_auth_health` function now includes `prompt_ready:bool` and `prompt_blocked_reason:string|null` in the data fields. `prompt_ready` is `true` when any auth credential is present (api_key, auth_token, or openai_key), `false` otherwise. `prompt_blocked_reason` is `"auth_missing"` when `prompt_ready` is false, `null` otherwise. The JSON error routing issue (stderr vs stdout) was already fixed by the main error handler routing JSON to stdout (line 408, #447). -692. **`dump-manifests --help --output-format json` is now correctly intercepted as help on current main, but the JSON help is message-only (`{kind, command, topic, message}`) and does not expose the manifest source contract (`--manifests-dir`, required upstream files, output schema, missing-manifest context fields, local/auth behavior); claws cannot discover how to preflight or repair manifest extraction without parsing prose or intentionally hitting `missing_manifests`** — dogfooded 2026-05-25 for the 01:30 Clawhip nudge at message `1508280974243266740`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. +692. **DONE — `dump-manifests --help --output-format json` is now correctly intercepted as help on current main, but the JSON help is message-only (`{kind, command, topic, message}`) and does not expose the manifest source contract (`--manifests-dir`, required upstream files, output schema, missing-manifest context fields, local/auth behavior); claws cannot discover how to preflight or repair manifest extraction without parsing prose or intentionally hitting `missing_manifests`** — dogfooded 2026-05-25 for the 01:30 Clawhip nudge at message `1508280974243266740`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. Reproduction: @@ -6873,7 +6873,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Make `check_auth_health()` call the same auth-source resolver (or a shared redaction-safe helper) used by runtime startup, so diagnostics cannot drift from actual request behavior. (b) Add structured fields to the auth check JSON: `effective_auth_source: "api_key" | "bearer_token" | "api_key_and_bearer" | "none"`, `headers_sent: ["x-api-key", "authorization_bearer"]` (names only, no secrets), and `both_anthropic_auth_env_vars_present: bool`. (c) When both are present, downgrade auth check from `ok` to at least `warn` unless product policy explicitly supports sending both; summary should say `both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are set; requests will send both x-api-key and bearer headers` with a hint to unset the stale/wrong one. (d) Mirror these fields into `status --output-format json`, not only `doctor`, because status is the lightweight preflight surface. (e) Add regression coverage for four states: none, API key only, bearer only, both. Assert the “both” state is not boolean-only and carries the combined-mode warning. **Acceptance check:** with both env vars set, `claw doctor --output-format json | jq -e '.checks[] | select(.name=="auth") | .effective_auth_source == "api_key_and_bearer" and (.headers_sent | index("x-api-key") and index("authorization_bearer")) and .status == "warn"'` should pass. Source: gaebal-gajae dogfood for 2026-05-24 15:00–16:30 Clawhip nudges; investigation was interrupted by subsequent nudge ticks, then finalized at 16:30 with code trace and ROADMAP entry. Coordination note: intentionally avoided F/CLAW_CONFIG_HOME because Jobdori publicly queued it as “next confirmed but unfiled”; this auth-precedence surface is orthogonal. -466. **Provider `*_BASE_URL` env vars are accepted as routing/transport configuration but `doctor` / `status` do zero validation and surface zero provenance: 24 malformed/unsupported values across `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, `XAI_BASE_URL`, and `DASHSCOPE_BASE_URL` all return `doctor_exit=0`, `has_failures=false`, `auth ok`, `config ok`, and `system ok`, even for `not-a-url`, `ftp://example.com`, `http://`, `http://localhost:99999`, `javascript:alert(1)`, and empty string. This makes the preflight surface say “green” while the next prompt call will fail later in the HTTP client / URL parser / provider edge, with no machine-readable clue which base URL env var poisoned the lane** — dogfooded 2026-05-24 for the 17:00–17:30 Clawhip nudge window (finalized for message `1508160182386167961`), reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`) in a clean isolated env. +466. **DONE — Provider `*_BASE_URL` env vars are accepted as routing/transport configuration but `doctor` / `status` do zero validation and surface zero provenance: 24 malformed/unsupported values across `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, `XAI_BASE_URL`, and `DASHSCOPE_BASE_URL` all return `doctor_exit=0`, `has_failures=false`, `auth ok`, `config ok`, and `system ok`, even for `not-a-url`, `ftp://example.com`, `http://`, `http://localhost:99999`, `javascript:alert(1)`, and empty string. This makes the preflight surface say “green” while the next prompt call will fail later in the HTTP client / URL parser / provider edge, with no machine-readable clue which base URL env var poisoned the lane** — dogfooded 2026-05-24 for the 17:00–17:30 Clawhip nudge window (finalized for message `1508160182386167961`), reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`) in a clean isolated env. Reproduction matrix (credential-free except fake API key env vars so auth check passes): @@ -7157,7 +7157,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Unsupported agents sub-actions should return a typed JSON error or explicit non-ok status such as `{type:"error", kind:"unsupported_agents_action", requested_action:"add", supported_actions:["list","help"], hint:"Native-agent mutation commands are not implemented; add agent files under a documented agents root or use ..."}` and exit non-zero. (b) Keep help fallback only for explicit `agents help` / `agents --help`; attempted mutations must not be reported as successful help. (c) If `add/remove/enable` are planned features, return `not_implemented` with nonzero exit and no file writes until the write-target/source-layer semantics exist. (d) Add parser/output tests for `agents add`, `agents remove`, and `agents enable` proving they are distinguishable from successful help and successful list. (e) Consider a shared helper for local route families (`agents`, `mcp`, maybe `skills`/`plugins`) so `unexpected` can never be the sole machine signal for unsupported actions. **Acceptance check:** `claw agents add demo -- /bin/echo hi --output-format json >/tmp/out 2>/tmp/err; test $? -ne 0 && jq -e '.kind == "unsupported_agents_action" and .requested_action == "add"' /tmp/err` should pass; currently exit is 0 and stdout is a help object. Source: gaebal-gajae dogfood for the 2026-05-24 20:30 Clawhip nudge. Coordination note: avoided Jobdori #680/session-sort, F/CLAW_CONFIG_HOME, already-covered MCP items, and prior agent items #328/#329/#346; targeted mutation semantics after route-sibling probe. -683. **Top-level `sandbox --help --output-format json` exits successfully but emits plain text help instead of JSON, so automation cannot discover sandbox isolation semantics without scraping prose** — dogfooded 2026-05-24 for the 21:00 Clawhip nudge at message `1508213026480849056`, reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`) in a clean isolated env. This continues the command-help JSON parity audit while avoiding already-filed #356 (`status --help --output-format json`) and #357 (`doctor --help --output-format json`). +683. **DONE — Top-level `sandbox --help --output-format json` exits successfully but emits plain text help instead of JSON, so automation cannot discover sandbox isolation semantics without scraping prose** — dogfooded 2026-05-24 for the 21:00 Clawhip nudge at message `1508213026480849056`, reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`) in a clean isolated env. This continues the command-help JSON parity audit while avoiding already-filed #356 (`status --help --output-format json`) and #357 (`doctor --help --output-format json`). Reproduction: @@ -7188,7 +7188,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Make `sandbox --help --output-format json` emit valid stdout JSON such as `{kind:"help", command:"sandbox", action:"help", usage:"claw sandbox [--output-format ]", purpose:"...", output_fields:[...], formats:["text","json"], related:["/sandbox","claw status"]}`. (b) Preserve the current aligned text only for text/default mode. (c) Add a `format:"json"` or schema/version field so callers can assert the help contract without parsing prose. (d) Add regression coverage proving sandbox help with JSON format parses as JSON and that normal `sandbox --output-format json` remains unchanged. (e) Consider sharing the fix with status/doctor (#356/#357), but keep sandbox covered explicitly to prevent partial help parity. **Acceptance check:** `claw sandbox --help --output-format json | jq -e '.kind == "help" and .command == "sandbox" and (.formats | index("json"))'` should pass; currently `jq` fails immediately because stdout begins with `Sandbox`. Source: gaebal-gajae dogfood for the 2026-05-24 21:00 Clawhip nudge. Coordination note: avoided Jobdori #680/session-sort, #681/#682 help-success mutation cluster, and existing help JSON items #325/#356/#357/#358/#380/#381; filed a fresh sandbox-specific preflight help parity gap. -684. **`init --help --output-format json` returns parseable JSON but keeps the init contract inside a prose `message`, unlike `export` help which exposes structured defaults/options; automation cannot discover which artifacts `init` creates, idempotency semantics, or next-step/recovery fields without scraping aligned text** — dogfooded 2026-05-24 for the 21:30 Clawhip nudge at message `1508220580053389436`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`, after discarding stale-debug-binary observations from `003b739d`/`e939777f`. +684. **DONE — `init --help --output-format json` returns parseable JSON but keeps the init contract inside a prose `message`, unlike `export` help which exposes structured defaults/options; automation cannot discover which artifacts `init` creates, idempotency semantics, or next-step/recovery fields without scraping aligned text** — dogfooded 2026-05-24 for the 21:30 Clawhip nudge at message `1508220580053389436`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`, after discarding stale-debug-binary observations from `003b739d`/`e939777f`. Reproduction: @@ -7233,7 +7233,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Extend `init --help --output-format json` with structured fields mirroring its real output and side-effect contract: `usage`, `purpose`, `formats:["text","json"]`, `creates:[{"name":".claw/","kind":"directory"}, ...]`, `idempotent:true`, `mutates_workspace:true`, `requires_confirmation:false`, `output_fields:["artifacts","created","skipped","updated","next_step","project_path"]`, `related:["claw status","claw doctor"]`, and optional `dry_run_available:false` until such a flag exists. (b) Keep `message` as human summary only. (c) Align command-help JSON schemas so side-effectful commands expose side-effect metadata consistently; `export` already proves richer help JSON is acceptable. (d) Add regression coverage proving `claw init --help --output-format json | jq '.creates[]?.name'` contains `.claw/`, `.claw.json`, `.gitignore`, and `CLAUDE.md`, and that it declares `idempotent:true` / `mutates_workspace:true`. **Acceptance check:** `claw init --help --output-format json | jq -e '.command=="init" and .mutates_workspace==true and .idempotent==true and ([.creates[].name] | index(".claw.json")) and ([.output_fields[]] | index("artifacts"))'` should pass; currently `.creates`, `.idempotent`, `.mutates_workspace`, and `.output_fields` are absent. Source: gaebal-gajae dogfood for the 2026-05-24 21:30 Clawhip nudge. Coordination note: avoided #420 after pre-grep showed Jobdori already tracked `plugins help`; avoided stale-binary candidates by rebuilding current `origin/main` before filing. -685. **`version --help --output-format json` returns only `{kind, command, topic, message}` and does not expose the provenance fields its actual `version --output-format json` command emits (`version`, `git_sha`, `target`, `build_date`) as structured `output_fields` / schema metadata, forcing dogfooders and wrappers to scrape prose before knowing which build-identity fields are available** — dogfooded 2026-05-24 for the 22:00 Clawhip nudge at message `1508228126088626216`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. This was checked after correcting an argv-loop mistake and verifying `claw version --output-format json | jq -r .git_sha` matched the intended source revision. +685. **DONE — `version --help --output-format json` returns only `{kind, command, topic, message}` and does not expose the provenance fields its actual `version --output-format json` command emits (`version`, `git_sha`, `target`, `build_date`) as structured `output_fields` / schema metadata, forcing dogfooders and wrappers to scrape prose before knowing which build-identity fields are available** — dogfooded 2026-05-24 for the 22:00 Clawhip nudge at message `1508228126088626216`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. This was checked after correcting an argv-loop mistake and verifying `claw version --output-format json | jq -r .git_sha` matched the intended source revision. Reproduction: @@ -7270,7 +7270,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Extend `version --help --output-format json` with structured fields such as `usage:"claw version [--output-format ]"`, `aliases:["claw --version","claw -V"]`, `purpose:"print build metadata"`, `formats:["text","json"]`, `output_fields:["kind","version","git_sha","target","build_date","message"]`, `provenance_fields:["git_sha","target","build_date"]`, `local_only:true`, `requires_credentials:false`, `mutates_workspace:false`, and `related:["claw doctor"]`. (b) Keep `message` as human summary only. (c) Add regression coverage proving `claw version --help --output-format json | jq '.output_fields'` includes `git_sha`, `target`, and `build_date`, and that `requires_credentials` is false. (d) Consider sharing this command-help schema pattern with status/doctor/sandbox/init/acp, but keep `version` covered explicitly because stale-binary checks depend on it. **Acceptance check:** `claw version --help --output-format json | jq -e '.command=="version" and .local_only==true and .requires_credentials==false and ([.output_fields[]] | index("git_sha") and index("target") and index("build_date"))'` should pass; currently `.local_only`, `.requires_credentials`, and `.output_fields` are absent. Source: gaebal-gajae dogfood for the 2026-05-24 22:00 Clawhip nudge. -686. **`doctor --help --output-format json` now returns parseable JSON on current main, but it is still message-only (`{kind, command, topic, message}`) and does not expose the diagnostic check schema, local-only/no-provider contract, or expected output fields (`checks[]`, check names, levels/statuses), so wrappers cannot discover how to consume the primary preflight surface without scraping prose or running the command first** — dogfooded 2026-05-24 for the 22:30 Clawhip nudge at message `1508235675546419210`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. +686. **DONE — `doctor --help --output-format json` now returns parseable JSON on current main, but it is still message-only (`{kind, command, topic, message}`) and does not expose the diagnostic check schema, local-only/no-provider contract, or expected output fields (`checks[]`, check names, levels/statuses), so wrappers cannot discover how to consume the primary preflight surface without scraping prose or running the command first** — dogfooded 2026-05-24 for the 22:30 Clawhip nudge at message `1508235675546419210`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. Reproduction: @@ -7314,7 +7314,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Extend `doctor --help --output-format json` with structured fields such as `usage:"claw doctor [--output-format ]"`, `purpose`, `formats:["text","json"]`, `related:["/doctor","claw --resume latest /doctor"]`, `local_only:true`, `requires_credentials:false`, `requires_provider_request:false`, `mutates_workspace:false`, `output_fields:["kind","status","checks","message"]`, `check_names:["auth","config","install_source","workspace","sandbox","system"]` (or a versioned stable/default list), `status_values:["ok","warn","fail"]`, and optional `schema_version`. (b) Keep `message` as human summary only. (c) Add regression coverage proving `claw doctor --help --output-format json | jq '.output_fields'` includes `checks`, and that `.requires_credentials == false`. (d) When new doctor checks land, update the help schema from the same registry/source used to build the report so help/check output cannot drift. **Acceptance check:** `claw doctor --help --output-format json | jq -e '.command=="doctor" and .local_only==true and .requires_credentials==false and ([.output_fields[]] | index("checks")) and ([.status_values[]] | index("warn"))'` should pass; currently those structured fields are absent. Source: gaebal-gajae dogfood for the 2026-05-24 22:30 Clawhip nudge. -687. **`status --help --output-format json` is JSON-valid on current main but message-only (`{kind, command, topic, message}`), while actual `status --output-format json` exposes a large preflight schema (`workspace`, `sandbox`, `allowed_tools`, `lane_board`, `model_source`, `usage`, etc.) that help does not describe; wrappers cannot discover status output fields or local/auth semantics without invoking status and reverse-engineering the payload** — dogfooded 2026-05-24 for the 23:00 Clawhip nudge at message `1508243229626204292`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. +687. **DONE — `status --help --output-format json` is JSON-valid on current main but message-only (`{kind, command, topic, message}`), while actual `status --output-format json` exposes a large preflight schema (`workspace`, `sandbox`, `allowed_tools`, `lane_board`, `model_source`, `usage`, etc.) that help does not describe; wrappers cannot discover status output fields or local/auth semantics without invoking status and reverse-engineering the payload** — dogfooded 2026-05-24 for the 23:00 Clawhip nudge at message `1508243229626204292`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. Reproduction: @@ -7350,7 +7350,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Extend `status --help --output-format json` with structured fields: `usage:"claw status [--output-format ]"`, `formats:["text","json"]`, `related:["/status","claw --resume latest /status"]`, `local_only:true`, `requires_credentials:false`, `requires_provider_request:false`, `mutates_workspace:false`, `output_fields:["kind","status","model","model_raw","model_source","permission_mode","allowed_tools","workspace","sandbox","usage","lane_board","config_load_error"]`, `workspace_fields:[...]`, `sandbox_fields:[...]`, `status_values:["ok","degraded",...]` or equivalent documented vocabulary, and `schema_version`. (b) Keep `message` as human summary only. (c) Derive help schema from the same status renderer structs/registry so newly added status fields do not drift from help. (d) Add regression coverage proving `claw status --help --output-format json | jq '.output_fields'` contains `workspace`, `sandbox`, `allowed_tools`, and that `workspace_fields` contains `branch_freshness` / `session_lifecycle`. **Acceptance check:** `claw status --help --output-format json | jq -e '.command=="status" and .local_only==true and .requires_credentials==false and ([.output_fields[]] | index("workspace") and index("sandbox") and index("allowed_tools")) and ([.workspace_fields[]] | index("branch_freshness"))'` should pass; currently those fields are absent. Source: gaebal-gajae dogfood for the 2026-05-24 23:00 Clawhip nudge. -688. **`sandbox --help --output-format json` is JSON-valid on current main but message-only (`{kind, command, topic, message}`), while actual `sandbox --output-format json` exposes the safety/trust schema (`active`, `supported`, `enabled`, namespace/network/filesystem flags, `allowed_mounts`, `fallback_reason`, markers) that help does not describe; automation cannot discover sandbox-state fields or their intended semantics without invoking sandbox and reverse-engineering the payload** — dogfooded 2026-05-24 for the 23:30 Clawhip nudge at message `1508250775162196070`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. +688. **DONE — `sandbox --help --output-format json` is JSON-valid on current main but message-only (`{kind, command, topic, message}`), while actual `sandbox --output-format json` exposes the safety/trust schema (`active`, `supported`, `enabled`, namespace/network/filesystem flags, `allowed_mounts`, `fallback_reason`, markers) that help does not describe; automation cannot discover sandbox-state fields or their intended semantics without invoking sandbox and reverse-engineering the payload** — dogfooded 2026-05-24 for the 23:30 Clawhip nudge at message `1508250775162196070`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. Reproduction: @@ -7395,7 +7395,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Extend `sandbox --help --output-format json` with structured fields: `usage:"claw sandbox [--output-format ]"`, `formats:["text","json"]`, `related:["/sandbox","claw status"]`, `local_only:true`, `requires_credentials:false`, `requires_provider_request:false`, `mutates_workspace:false`, `output_fields:["kind","active","supported","enabled","requested_namespace","active_namespace","requested_network","active_network","filesystem_active","filesystem_mode","allowed_mounts","fallback_reason","in_container","markers"]`, `component_fields:["namespace","network","filesystem"]`, `filesystem_modes:[...]`, and `schema_version`. (b) Add a machine-readable `active_semantics` field, e.g. `"all_requested_components_active"` or `"any_component_active"`, matching the eventual #448 fix. (c) Keep `message` as human summary only. (d) Derive the help schema from the same sandbox report struct/registry used to render the payload so added sandbox fields cannot drift from help. **Acceptance check:** `claw sandbox --help --output-format json | jq -e '.command=="sandbox" and .local_only==true and .requires_credentials==false and ([.output_fields[]] | index("filesystem_active") and index("allowed_mounts") and index("fallback_reason")) and (.active_semantics | type == "string")'` should pass; currently those structured fields are absent. Source: gaebal-gajae dogfood for the 2026-05-24 23:30 Clawhip nudge. -689. **`acp --help --output-format json` is JSON-valid but message-only (`{kind, command, topic, message}`), even though the actual ACP discoverability surface (`claw --output-format json acp`, `acp serve`, `--acp`, `-acp`) already exposes a rich structured status contract (`supported:false`, `phase`, `protocol`, `serve_alias_only`, `contracts`, `recommended_workflows`, `schema_version`, tracking IDs); editor wrappers cannot discover the ACP/Zed non-daemon contract from help without invoking ACP status and reverse-engineering the payload** — dogfooded 2026-05-25 for the 00:00 Clawhip nudge at message `1508258328189472908`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. +689. **DONE — `acp --help --output-format json` is JSON-valid but message-only (`{kind, command, topic, message}`), even though the actual ACP discoverability surface (`claw --output-format json acp`, `acp serve`, `--acp`, `-acp`) already exposes a rich structured status contract (`supported:false`, `phase`, `protocol`, `serve_alias_only`, `contracts`, `recommended_workflows`, `schema_version`, tracking IDs); editor wrappers cannot discover the ACP/Zed non-daemon contract from help without invoking ACP status and reverse-engineering the payload** — dogfooded 2026-05-25 for the 00:00 Clawhip nudge at message `1508258328189472908`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. Reproduction: @@ -7446,7 +7446,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Extend `acp --help --output-format json` with structured fields mirroring the ACP status contract: `usage:"claw acp [serve] [--output-format ]"`, `aliases:["acp","--acp","-acp"]`, `formats:["text","json"]`, `related:["ROADMAP #64a","ROADMAP #76","claw --help"]`, `local_only:true`, `requires_credentials:false`, `requires_provider_request:false`, `mutates_workspace:false`, `serve_starts_daemon:false`, `output_fields:["kind","status","supported","phase","protocol","contracts","recommended_workflows","schema_version"]`, `status_values:["unsupported"]`, `phase_values:["discoverability_only"]`, and `protocol_fields:["daemon","endpoint","json_rpc","name","serve_starts_daemon"]`. (b) Derive help metadata from the same ACP status struct/registry so help and status cannot drift. (c) Keep `message` as the human summary only. **Acceptance check:** `claw acp --help --output-format json | jq -e '.command=="acp" and .local_only==true and .requires_credentials==false and .serve_starts_daemon==false and ([.output_fields[]] | index("protocol") and index("contracts")) and ([.aliases[]] | index("--acp"))'` should pass; currently those fields are absent. Source: gaebal-gajae dogfood for the 2026-05-25 00:00 Clawhip nudge. -690. **`bootstrap-plan --help --output-format json` is now correctly intercepted as help on current main, but the JSON help is message-only (`{kind, command, topic, message}`) while actual `bootstrap-plan --output-format json` exposes the ordered startup phase contract in `phases[]`; startup wrappers cannot discover phase names, ordering semantics, or local/auth behavior from help without invoking the plan and reverse-engineering the payload** — dogfooded 2026-05-25 for the 00:30 Clawhip nudge at message `1508265874824626176`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. +690. **DONE — `bootstrap-plan --help --output-format json` is now correctly intercepted as help on current main, but the JSON help is message-only (`{kind, command, topic, message}`) while actual `bootstrap-plan --output-format json` exposes the ordered startup phase contract in `phases[]`; startup wrappers cannot discover phase names, ordering semantics, or local/auth behavior from help without invoking the plan and reverse-engineering the payload** — dogfooded 2026-05-25 for the 00:30 Clawhip nudge at message `1508265874824626176`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. Reproduction: @@ -7492,7 +7492,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) **Required fix shape:** (a) Extend `bootstrap-plan --help --output-format json` with structured fields: `usage:"claw bootstrap-plan [--output-format ]"`, `formats:["text","json"]`, `related:["claw doctor","claw status"]`, `local_only:true`, `requires_credentials:false`, `requires_provider_request:false`, `mutates_workspace:false`, `output_fields:["kind","phases"]`, `phase_order:["CliEntry","FastPathVersion","StartupProfiler","SystemPromptFastPath","ChromeMcpFastPath","DaemonWorkerFastPath","BridgeFastPath","DaemonFastPath","BackgroundSessionFastPath","TemplateFastPath","EnvironmentRunnerFastPath","MainRuntime"]`, `phase_count:12`, `ordering_semantics:"execution_order_before_main_runtime"`, and `schema_version`. (b) Derive help phase metadata from the same phase registry/vector used by `bootstrap-plan` output so additions cannot drift. (c) Keep `message` as human summary only. **Acceptance check:** `claw bootstrap-plan --help --output-format json | jq -e '.command=="bootstrap-plan" and .local_only==true and .requires_credentials==false and ([.output_fields[]] | index("phases")) and (.phase_order[0] == "CliEntry") and (.ordering_semantics | type == "string")'` should pass; currently those structured fields are absent. Source: gaebal-gajae dogfood for the 2026-05-25 00:30 Clawhip nudge. -691. **`system-prompt --help --output-format json` is now correctly intercepted as help on current main, but the JSON help is message-only (`{kind, command, topic, message}`) and does not expose the dangerous option contract (`--cwd`, `--date`), validation expectations, or actual output schema (`kind`, `message`, `sections`); wrappers cannot safely discover how to render deterministic system prompts or validate tainted inputs without parsing prose or invoking the prompt renderer** — dogfooded 2026-05-25 for the 01:00 Clawhip nudge at message `1508273429000880210`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. +691. **DONE — `system-prompt --help --output-format json` is now correctly intercepted as help on current main, but the JSON help is message-only (`{kind, command, topic, message}`) and does not expose the dangerous option contract (`--cwd`, `--date`), validation expectations, or actual output schema (`kind`, `message`, `sections`); wrappers cannot safely discover how to render deterministic system prompts or validate tainted inputs without parsing prose or invoking the prompt renderer** — dogfooded 2026-05-25 for the 01:00 Clawhip nudge at message `1508273429000880210`, reproduced on a freshly rebuilt current `origin/main` binary (`git_sha f8e1bb726`) from `/tmp/cc-probe-main-2130`. Active claw-code sessions: none. Reproduction: @@ -7561,7 +7561,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 698. **DONE — config warning dedup already implemented** — `emit_config_warning_once` at `config.rs:25` uses `OnceLock>>` to deduplicate warnings across multiple `load()` calls. JSON mode suppresses stderr warnings via `SUPPRESS_CONFIG_WARNINGS_STDERR`. -699. **`bootstrap-plan` and `dump-manifests` JSON/help probes fall through to prompt/auth instead of local command dispatch unless global flags are positioned just so; with normal subcommand-style argv they either hang behind the spinner or return `missing_credentials`, making local startup/manifest introspection non-local** — dogfooded 2026-05-25 on `11a6e081a` after the ROADMAP #458 envelope sweep. Reproduction with the freshly rebuilt debug binary: `./rust/target/debug/claw bootstrap-plan --output-format json 0)'` and the analogous dump-manifests/help probes must return within 1s without credentials. Source: gaebal-gajae dogfood for the 2026-05-25 07:30 Clawhip nudge. +699. **DONE — `bootstrap-plan` and `dump-manifests` JSON/help probes fall through to prompt/auth instead of local command dispatch unless global flags are positioned just so; with normal subcommand-style argv they either hang behind the spinner or return `missing_credentials`, making local startup/manifest introspection non-local** — dogfooded 2026-05-25 on `11a6e081a` after the ROADMAP #458 envelope sweep. Reproduction with the freshly rebuilt debug binary: `./rust/target/debug/claw bootstrap-plan --output-format json 0)'` and the analogous dump-manifests/help probes must return within 1s without credentials. Source: gaebal-gajae dogfood for the 2026-05-25 07:30 Clawhip nudge. 700. **DONE — help JSON already has status field** — `print_help` at line 13547 emits `{kind:"help", action:"help", status:"ok", message:...}`. `session_list` kind renamed to `sessions` in earlier work. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index d1f4b50c..686a6802 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -3625,6 +3625,7 @@ fn render_doctor_report( Ok(DoctorReport { checks: vec![ check_auth_health(), + check_base_url_health(), check_config_health(&config_loader, config.as_ref()), check_mcp_validation_health(&mcp_validation), check_hook_validation_health(&hook_validation), @@ -3865,6 +3866,50 @@ fn check_auth_health() -> DiagnosticCheck { } } +/// #466: validate provider BASE_URL env vars +fn check_base_url_health() -> DiagnosticCheck { + let base_url_vars = [ + ("ANTHROPIC_BASE_URL", "https://api.anthropic.com"), + ("OPENAI_BASE_URL", "https://api.openai.com"), + ("XAI_BASE_URL", "https://api.x.ai"), + ("DASHSCOPE_BASE_URL", "https://dashscope.aliyuncs.com"), + ]; + let mut issues: Vec = Vec::new(); + let mut details: Vec = Vec::new(); + for (var_name, default_url) in &base_url_vars { + if let Ok(value) = env::var(var_name) { + let trimmed = value.trim(); + if trimmed.is_empty() { + issues.push(format!("{var_name} is empty")); + details.push(format!( + "{var_name} empty (will use default: {default_url})" + )); + } else if !trimmed.starts_with("http://") && !trimmed.starts_with("https://") { + issues.push(format!("{var_name}={trimmed} is not a valid HTTP(S) URL")); + details.push(format!("{var_name} invalid ({trimmed})")); + } else { + details.push(format!("{var_name} {trimmed}")); + } + } + } + if issues.is_empty() { + DiagnosticCheck::new( + "Base URLs", + DiagnosticLevel::Ok, + "provider base URL env vars are valid or unset", + ) + .with_details(details) + } else { + DiagnosticCheck::new( + "Base URLs", + DiagnosticLevel::Warn, + format!("{} base URL issue(s) found", issues.len()), + ) + .with_details(details) + .with_hint("Fix the reported BASE_URL env vars or unset them to use provider defaults.") + } +} + fn check_config_health( config_loader: &ConfigLoader, config: Result<&runtime::RuntimeConfig, &runtime::ConfigError>, @@ -10011,6 +10056,82 @@ fn render_doctor_help_json() -> serde_json::Value { }) } +/// #683-#692: extract structured metadata from help prose +fn extract_help_metadata( + topic: LocalHelpTopic, +) -> ( + Option, // usage + Option, // purpose + Option, // output description + Option>, // formats + Option>, // related + Option>, // aliases + bool, // local_only + bool, // requires_credentials +) { + let text = render_help_topic(topic); + let mut usage = None; + let mut purpose = None; + let mut output_desc = None; + let formats = Some(vec!["text".to_string(), "json".to_string()]); + let mut related = None; + let mut aliases = None; + let local_only = matches!( + topic, + LocalHelpTopic::Status + | LocalHelpTopic::Sandbox + | LocalHelpTopic::Doctor + | LocalHelpTopic::Version + | LocalHelpTopic::State + | LocalHelpTopic::Init + | LocalHelpTopic::Export + | LocalHelpTopic::SystemPrompt + | LocalHelpTopic::DumpManifests + | LocalHelpTopic::BootstrapPlan + ); + for line in text.lines() { + let trimmed = line.trim(); + if let Some(rest) = trimmed.strip_prefix("Usage") { + let value = rest.trim(); + if !value.is_empty() { + usage = Some(value.to_string()); + } + } else if let Some(rest) = trimmed.strip_prefix("Purpose") { + purpose = Some(rest.trim().to_string()); + } else if let Some(rest) = trimmed.strip_prefix("Output") { + output_desc = Some(rest.trim().to_string()); + } else if let Some(rest) = trimmed.strip_prefix("Aliases") { + let parts: Vec = rest + .split('·') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + if !parts.is_empty() { + aliases = Some(parts); + } + } else if let Some(rest) = trimmed.strip_prefix("Related") { + let parts: Vec = rest + .split('·') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + if !parts.is_empty() { + related = Some(parts); + } + } + } + ( + usage, + purpose, + output_desc, + formats, + related, + aliases, + local_only, + !local_only, + ) +} + fn render_help_topic_json(topic: LocalHelpTopic) -> serde_json::Value { if topic == LocalHelpTopic::Export { return render_export_help_json(); @@ -10019,14 +10140,30 @@ fn render_help_topic_json(topic: LocalHelpTopic) -> serde_json::Value { return render_doctor_help_json(); } - json!({ + // #683-#692: extract structured metadata from help prose for machine consumption + let (usage, purpose, output_desc, formats, related, aliases, local_only, requires_credentials) = + extract_help_metadata(topic); + let mut obj = serde_json::json!({ "kind": "help", "action": "help", "status": "ok", "topic": local_help_topic_command(topic), "command": local_help_topic_command(topic), "message": render_help_topic(topic), - }) + "usage": usage, + "purpose": purpose, + "formats": formats, + "related": related, + "local_only": local_only, + "requires_credentials": requires_credentials, + }); + if let Some(desc) = output_desc { + obj["output_fields"] = serde_json::Value::String(desc); + } + if let Some(a) = aliases { + obj["aliases"] = serde_json::json!(a); + } + obj } fn print_help_topic( diff --git a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs index 381aa58e..f84036f1 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -1476,7 +1476,7 @@ fn doctor_and_resume_status_emit_json_when_requested() { .is_some_and(|available| available.iter().any(|name| name == "web_fetch"))); let checks = doctor["checks"].as_array().expect("doctor checks"); - assert_eq!(checks.len(), 11); + assert_eq!(checks.len(), 12); let check_names = checks .iter() .map(|check| { @@ -1496,6 +1496,7 @@ fn doctor_and_resume_status_emit_json_when_requested() { check_names, vec![ "auth", + "base urls", "config", "mcp validation", "hook validation",