Skip to content

docs(roadmap): add #463 — removed subcommands (login/logout) emit kind:unknown with hint:null; classifier orphan from #37×#77 interaction#3076

Open
Yeachan-Heo wants to merge 1 commit into
mainfrom
docs/roadmap-463-removed-subcommand-kind-unknown
Open

docs(roadmap): add #463 — removed subcommands (login/logout) emit kind:unknown with hint:null; classifier orphan from #37×#77 interaction#3076
Yeachan-Heo wants to merge 1 commit into
mainfrom
docs/roadmap-463-removed-subcommand-kind-unknown

Conversation

@Yeachan-Heo
Copy link
Copy Markdown
Contributor

ROADMAP pinpoint #463claw login/claw logout removed-subcommand sentinel mis-bucketed as kind:unknown with hint:null

Dogfooded for the 2026-05-24 13:30 Clawhip pinpoint nudge (message 1508099780230906027).

The bug in two envelopes

$ claw login --output-format json
{"error":"`claw login` has been removed. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.","hint":null,"kind":"unknown","type":"error"}

$ claw logout --output-format json
{"error":"`claw logout` has been removed. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.","hint":null,"kind":"unknown","type":"error"}

Cross-input classifier audit

Input exit kind hint What actually happened
xyznotreal 1 missing_credentials null LLM-prompt fallthrough (#108 territory)
totally-fake-cmd 1 missing_credentials null Same fallthrough
login (removed) 1 unknown null Intercepted (good), wrong-kinded + hint discarded (this PR)
logout (removed) 1 unknown null Same as login
plugins (formerly #78) 0 (works now) Pre-grep confirmed #78 has been wired

Three different code paths, three different misclassifications. This PR addresses the middle two.

Root cause (traced)

main.rs:951parse_args returns the sentinel as a plain String:

"login" | "logout" => Err(removed_auth_surface_error(rest[0].as_str())),

main.rs:1150-1154 — single-line sentinel:

fn removed_auth_surface_error(command_name: &str) -> String {
    format!("`claw {command_name}` has been removed. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.")
}

main.rs:253-286 — classifier has 13 specific kinds, has been removed matches none:

fn classify_error_kind(message: &str) -> &'static str {
    if message.contains("missing Anthropic credentials") { "missing_credentials" }
    else if message.contains("Manifest source files are missing") { "missing_manifests" }
    // ... 11 more specific patterns ...
    else { "unknown" }   // ← removed-subcommand sentinel lands here
}

main.rs:290-295split_error_hint splits on \n; sentinel is single-line, so hint is always None.

Verification grep: grep -nE 'removed_subcommand|"removed"' rust/crates/rusty-claude-cli/src/main.rs returns zero hits. Test coverage at main.rs:11793-11794 only asserts --help negative-presence:

assert!(!help.contains("claw login"));
assert!(!help.contains("claw logout"));

No envelope-shape test exists.

Why distinct from existing items

This is a #37×#77 classifier-orphan pattern: features added after the classifier shipped never get registered, silently degrading their envelope quality.

Why it matters

  1. CI fingerprinting breakage: if claw cmd --output-format json | jq -e '.kind == "unknown"' treats removed-command typos as unrecoverable internal errors — bad escalation triage.
  2. hint field discarded is the same anti-pattern as 火钳流明,合影合影。后厂村发来贺电 #459 / 合影~ #455 / MARK #79 / 合影 #326 / 合影 #462 — structured field exists, data exists, envelope drops it.
  3. Classifier completeness debt: no compile-time check that every Err(...) sentinel has a matching classify_error_kind branch. Sweep would surface other orphans.
  4. Asymmetric remediation guidance: error string contains remediation; hint field is null. Human sees it, machine doesn't.

Required fix shape

(a) Structured sentinel emission: replace Err(removed_auth_surface_error(...)) with a typed variant (e.g., CliAction::Error(CliError::RemovedSubcommand { name, replacement_env_vars })) so JSON layer can serialize {"kind": "removed_subcommand", "subcommand": "login", "hint": "..."} without classifier-string-matching.

(b) Add "removed_subcommand" to classify_error_kind as defensive fallback keyed on "has been removed" substring.

(c) Make removed_auth_surface_error two-line so split_error_hint populates hint even on the string-only path.

(d) Envelope-shape regression tests in output_format_contract.rs: assert claw login --output-format json produces {"kind": "removed_subcommand", "hint": <non-null string>}.

(e) Sweep for other classifier orphans: grep -rnE 'Err\(format!|Err\("' rust/crates/rusty-claude-cli/src/ and cross-check against classify_error_kind.

Acceptance check

claw login --output-format json 2>&1 | jq -e '.kind == "removed_subcommand" and (.hint | type == "string")'

Should print true (currently .kind == "unknown" and .hint == null → exit 1).

Method honesty

Pre-grep gate filtered 9 hypotheses → 3 fresh (E=login/logout envelope, F=CLAW_CONFIG_HOME validation across 5 silent-failure modes, G=skills empty envelope).

  • G confirmed working correctly — no pinpoint.
  • F deferred — spans 5 distinct silent-failure modes (/nonexistent silent ok, /etc silent ok, /dev/null/cfg degraded but exit=0, empty string silent ok, regular file silent ok) and warrants its own consolidated entry.
  • E selected as tightest single-function fix with most concrete CI-impact angle.

Also caught one near-miss mid-investigation: my E1 reading was contaminated by stdout from a prior shell command. Required clean re-isolation with separate file handles to confirm exit=1 (correct) and stderr-only routing (correct) — the actual bugs are envelope-shape, not exit-code.


[repo owner's gaebal-gajae (clawdbot) 🦞]

…d:unknown with hint:null; classifier never registered the #37 sentinel (#77 classifier orphan)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants