Compare commits

..

1 Commits

Author SHA1 Message Date
Yeachan-Heo
21934ef070 docs(roadmap): add #685 — version help json lacks provenance schema 2026-05-24 22:01:24 +00:00

View File

@@ -6429,44 +6429,39 @@ 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.
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:0017: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.
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.
Reproduction matrix (credential-free except fake API key env vars so auth check passes):
Reproduction:
```bash
for var in ANTHROPIC_BASE_URL OPENAI_BASE_URL XAI_BASE_URL DASHSCOPE_BASE_URL; do
for url in 'not-a-url' 'ftp://example.com' 'http://' 'http://localhost:99999' 'javascript:alert(1)' ''; do
env -i HOME=/tmp/iso24/home ANTHROPIC_API_KEY=sk-ant-fake OPENAI_API_KEY=sk-openai-fake \
"$var=$url" PATH=/usr/bin:/bin TERM=dumb \
claw doctor --output-format json
done
done
```
Every row produced the same health shape:
```text
doctor_exit=0 stderr_bytes=0
has_failures false
auth ok 'supported auth env vars are configured'
config ok 'no config files present; defaults are active'
system ok 'captured local runtime metadata'
```
No diagnostic says the lane is configured to talk to `javascript:alert(1)`, `ftp://example.com`, `http://`, or an invalid port `99999`.
**Root cause traced:** provider metadata knows which env var controls each provider's base URL (`rust/crates/api/src/providers/mod.rs:42-43`, with `base_url_env` / `default_base_url` fields), and the Anthropic client reads `ANTHROPIC_BASE_URL` directly in `anthropic.rs:765-766`:
```rust
pub fn read_base_url() -> String {
std::env::var("ANTHROPIC_BASE_URL").unwrap_or_else(|_| DEFAULT_BASE_URL.to_string())
$ env -i HOME=/tmp/iso40/home PATH=/usr/bin:/bin TERM=dumb \
claw version --help --output-format json
{
"command": "version",
"kind": "help",
"message": "Version\n Usage claw version [--output-format <format>]\n Aliases claw --version · claw -V\n Purpose print the claw CLI version and build metadata\n Formats text (default), json\n Related claw doctor (full build/auth/config diagnostic)",
"topic": "version"
}
```
OpenAI-compat providers carry the same metadata in `openai_compat.rs` (`XAI_BASE_URL`, `OPENAI_BASE_URL`, `DASHSCOPE_BASE_URL`). But `check_auth_health()` only checks auth booleans, `check_config_health()` only checks config files/MCP counts, and `check_system_health()` only reports local runtime metadata. There is no provider-endpoint check and no `base_url_source` field in `status`. The runtime will later concatenate/parse/use these env vars as actual URLs (`anthropic.rs:470` builds `{base_url}/v1/messages`), but the preflight surfaces do not validate scheme, host, parseability, or port range.
The command returns valid JSON, but all actionable contract details are embedded in prose. There is no structured `usage`, `aliases`, `purpose`, `formats`, `related`, `output_fields`, `provenance_fields`, `local_only`, or `requires_credentials:false` field.
**Why distinct from existing items:** ROADMAP #28/#29 cover provider routing and auth-copy issues, including OpenRouter guidance and prefix routing, but not malformed base URL validation. ROADMAP #248 asks non-interactive prompt lifecycle events to include provider/model/base-url identity after a prompt starts; this entry is the *preflight* counterpart: `doctor` / `status` should surface bad base URLs before any prompt/API call. ROADMAP #465 covers effective auth source when both Anthropic auth env vars are set; this entry covers endpoint configuration across all provider env vars. ROADMAP #111 says `/providers` should list providers/base URLs/reachability, but `/providers` is a slash-command spec mismatch; this entry is specifically about the existing `doctor`/`status` green-light surfaces ignoring base URL env vars. No existing entry documents that every malformed `*_BASE_URL` value tested leaves doctor green.
Contrast with the real command on the same binary:
**Why this matters:** (1) **Startup friction:** users configuring OpenRouter/Ollama/local proxy inevitably touch `OPENAI_BASE_URL`. A one-character typo (`http://`, trailing junk, unsupported scheme) currently passes doctor and fails only during a live prompt. (2) **Automation cannot preflight lanes.** Clawhip wants to know whether a lane is safe before spending tokens / starting a long prompt. Green doctor with poisoned base URL is false confidence. (3) **Security posture:** accepting `javascript:` / `ftp:` / empty string as silently “configured” is not just UX badness; provider clients should only ever use HTTP(S) URLs, and diagnostics should reject or warn on anything else. (4) **The metadata already exists.** Provider metadata names every env var and default URL; the missing layer is just validation and redaction-safe surfacing. (5) **Base URL is provider identity.** Status currently reports model and permission mode, but not where requests will be sent. For OpenAI-compatible providers especially, `OPENAI_BASE_URL` is the difference between OpenAI, OpenRouter, Ollama, local proxy, or malicious typo.
```bash
$ claw version --output-format json
{
"kind": "version",
"version": "0.1.0",
"git_sha": "f8e1bb726",
"target": "x86_64-unknown-linux-gnu",
"build_date": "2026-05-24",
"message": "Claw Code\n Version ..."
}
```
**Required fix shape:** (a) Add a provider endpoint diagnostics check to `doctor` that iterates provider metadata, reads each `*_BASE_URL` env var if present, trims it, parses it with `Url`, and validates `scheme in {http, https}`, non-empty host, valid port, and no unsupported schemes. Empty string should be treated as unset or explicit invalid with a dedicated warning, not silent ok. (b) Add redaction-safe fields to `status --output-format json`: active provider, `base_url_env`, `base_url_source: "default" | "env"`, `base_url_valid`, `base_url_scheme`, `base_url_host`, and `base_url_error` if invalid. Do not include credentials or path secrets; host/scheme are enough. (c) When the selected model/provider is affected by an invalid base URL, `doctor` should be `warn` or `fail` and `status.status` should be `degraded`, not `ok`. (d) Add tests for the 24-row matrix above plus a valid local URL (`http://127.0.0.1:11434/v1`) and valid HTTPS URL. (e) Optional: `/providers` (when fixed from #111) should reuse the same endpoint validation so base URL truth has one source. **Acceptance check:** `env OPENAI_API_KEY=sk-test OPENAI_BASE_URL=javascript:alert\(1\) claw doctor --output-format json | jq -e '.checks[] | select(.name=="providers" or .name=="provider_endpoints") | .status != "ok" and (.details[]? | test("OPENAI_BASE_URL"))'` should pass; currently there is no such check and doctor is green. Source: gaebal-gajae dogfood for the 2026-05-24 17:00/17:30 Clawhip nudges. Coordination note: still avoided F/CLAW_CONFIG_HOME because Jobdori publicly queued it; this endpoint-validation surface is orthogonal and credential-free.
**Why distinct from existing items:** #325 covers top-level `help --output-format json` being prose wrapped in JSON. #356/#357/#683 covered status/doctor/sandbox help-format gaps; on current main these now return parseable command-help JSON, but still largely prose-only. #684 covers `init` specifically because it is side-effectful and lacks structured write/idempotency metadata. #685 is version-specific: `version` is the build-provenance surface dogfooders use to prove whether a finding is from current code or a stale debug binary (see #324 stale-binary provenance). Its help JSON should say, machine-readably, which provenance fields exist.
**Why this matters:** Build identity is the first trust check for every dogfood report. A wrapper should be able to discover from help that `version --output-format json` includes `git_sha`, `target`, `build_date`, and `version`, and that it is local-only / credential-free. Today it must either scrape the aligned `message` string or blindly invoke `version` and reverse-engineer the schema. That weakens exactly the stale-binary guard used to avoid false ROADMAP entries.
**Required fix shape:** (a) Extend `version --help --output-format json` with structured fields such as `usage:"claw version [--output-format <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.