fix(spoof): CCH x-anthropic-billing-header + complete auto-discovery (v0.1.15+v0.1.16)#13
Merged
Merged
Conversation
…(v0.1.15+v0.1.16) After 2 weeks of agent-launch failures and four wrong-theory hotfixes, side-by-side comparison with claurst (third-party Rust port of Claude Code) revealed the actual root cause: archon-cli's spoof was missing the Claude Code billing/anti-spoof header. Anthropic was returning 429 with no retry-after (matching the fallback default in classify_error) — not a real rate limit, but Anthropic rejecting our request as an invalid Claude Code attestation. This commit lands v0.1.15 (CCH fix + initial discovery wiring) plus v0.1.16 (complete auto-discovery loop) together as a single PR. Bug #1 (v0.1.15) — broken billing header since initial commit: archon-cli's identity.rs::billing_header() built a billing string in the right format but inserted it as a SYSTEM PROMPT TEXT BLOCK (invisible to Anthropic's HTTP-header billing layer) and omitted the cch=<hash> field entirely. compute_fingerprint used SHA256 of three cherry-picked characters from the first user message — bears no relation to xxhash64-of-body that Anthropic actually validates. Fix mirrors claurst exactly: - New crates/archon-llm/src/cch.rs: xxhash64(body, 0x6E52736AC806831E) & 0xFFFFF, formatted "cch=<5hex>". - anthropic.rs: spoof mode now adds HTTP header x-anthropic-billing-header: "cc_version=...; cc_entrypoint=...; cch= <hash>; cc_workload=...;" computed from the actual serialized body bytes per request. - identity.rs: deleted compute_fingerprint, deleted billing_header() method, removed the broken billing-as-system-prompt-block insertion. - Cargo.toml: + xxhash-rust = { workspace = true, features = ["xxh64"] } v0.1.15 also wired discover_claude_code_identity() into IdentityProvider construction (it had existed but was only called from tests). Bug #2 (v0.1.16) — discovery infrastructure unfinished: Even after v0.1.15 wired discovery into production, version discovery from binary strings did NOT work on Bun-templated versions like Claude Code v2.1.119+ — the version is templated at runtime, not a literal string. Spoof would fall back to the hardcoded spoof_version="2.1.89" config default (30 patch versions stale). Fix: - identity.rs: new version_from_package_json() — walks bin/claude → ../package.json, extracts the npm version field. Reliable on v2.1.119. - discover_claude_code_identity() falls back to package.json when binary strings yield no version. - discover_claude_code_identity() now memoised via std::sync::OnceLock (was walking 234M binary on every call). - New resolve_spoof_version / resolve_user_agent / resolve_entrypoint helpers, each returning (value, source) tuples for log surfacing. - session.rs: both IdentityMode::Spoof construction sites now use the resolvers and emit info!("Spoof identity resolved", version, version_source, ...) at startup. Tests added (bundled in this PR): - crates/archon-llm/src/cch.rs: 4 unit tests (format, determinism, body-sensitivity, cross-reference vector) - crates/archon-llm/tests/cch_header_sent.rs: spoof mode includes x-anthropic-billing-header; clean mode omits it - crates/archon-llm/tests/cch_matches_body.rs: hash recomputed per request from actual serialized body (mutating after hashing fails) - crates/archon-llm/tests/diagnostic_surfacing.rs: 429 response body surfaces through LlmError into TUI failure line - crates/archon-llm/tests/identity_discovery.rs: extraction from synthetic strings + cch_required signal detection - crates/archon-llm/tests/identity_resolution.rs: 12 tests for priority resolution (discovered > config > fallback) for version, entrypoint, user-agent + memoization invariant Verified end-to-end: - cargo check --workspace --tests -j1 --offline: exit 0 - cargo nextest run --workspace -j1 -- --test-threads=2: ALL PASS - cargo fmt --check: PASS - cargo build --release --bin archon -j1: PASS Pending: real-binary TUI smoke test (Steven runs interactively). Spoof identity log line should emit at startup with the actual installed Claude Code version (v2.1.119, not the hardcoded 2.1.89). Version: 0.1.14 -> 0.1.16.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
THE bug after 2 weeks of agent-launch failures and four wrong-theory hotfixes. Side-by-side comparison with claurst (third-party Rust port at `/tmp/claurst/`) revealed the actual root cause: archon-cli's spoof was missing the Claude Code billing/anti-spoof header. Anthropic was returning 429 with no `retry-after` (matching the fallback default in `classify_error` exactly) — not a real rate limit, but Anthropic rejecting our request as an invalid Claude Code attestation.
Bundles v0.1.15 (CCH fix + initial discovery wiring) + v0.1.16 (auto-discovery completion) into one PR — version 0.1.14 → 0.1.16.
Bug #1 — broken billing header since initial commit (v0.1.15)
`identity.rs::billing_header()` built a billing string in the right format but inserted it as a SYSTEM PROMPT TEXT BLOCK (invisible to Anthropic's HTTP-header billing layer) and omitted the `cch=` field entirely. `compute_fingerprint` used SHA256 of three cherry-picked characters from the first user message — bears no relation to xxhash64-of-body that Anthropic actually validates.
Fix: mirror claurst exactly. New `crates/archon-llm/src/cch.rs` with `xxhash64(body, 0x6E52736AC806831E) & 0xFFFFF` formatted as `cch=<5hex>`. `anthropic.rs` adds HTTP header `x-anthropic-billing-header` per request, computed from the actual serialized body bytes. Deleted the broken `compute_fingerprint` and the system-prompt-block insertion.
Bug #2 — discovery infrastructure unfinished (v0.1.16)
Even after wiring `discover_claude_code_identity()` into production, version discovery from binary strings did NOT work on Bun-templated versions (Claude Code v2.1.119+) — the version is templated at runtime, not a literal in the binary's strings table. Spoof would fall back to the hardcoded `spoof_version="2.1.89"` config default (30 patch versions stale).
Fix: new `version_from_package_json()` walks `bin/claude → ../package.json` and extracts the npm version field. Reliable on v2.1.119. Plus memoization via `std::sync::OnceLock` (was walking 234M binary on every call), and resolver helpers (`resolve_spoof_version` / `resolve_user_agent` / `resolve_entrypoint`) returning `(value, source)` tuples for log surfacing. `session.rs` emits `info!("Spoof identity resolved", version, version_source, ...)` at startup.
Test plan