Skip to content

fix(spoof): CCH x-anthropic-billing-header + complete auto-discovery (v0.1.15+v0.1.16)#13

Merged
ste-bah merged 1 commit into
mainfrom
fix/spoof-cch-and-discovery-v0.1.16
Apr 27, 2026
Merged

fix(spoof): CCH x-anthropic-billing-header + complete auto-discovery (v0.1.15+v0.1.16)#13
ste-bah merged 1 commit into
mainfrom
fix/spoof-cch-and-discovery-v0.1.16

Conversation

@ste-bah

@ste-bah ste-bah commented Apr 27, 2026

Copy link
Copy Markdown
Owner

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

  • cargo check --workspace --tests -j1 --offline: exit 0
  • cargo nextest run --workspace -j1 -- --test-threads=2: ALL PASS
    • 4 cch unit tests (format, determinism, body-sensitivity, cross-reference vector)
    • cch_header_sent: spoof mode includes header; clean mode omits
    • cch_matches_body: hash recomputed per request from actual serialized body
    • diagnostic_surfacing: 429 response body surfaces through LlmError → TUI
    • identity_discovery: synthetic-strings extraction + cch_required detection
    • identity_resolution: 12 tests for priority resolution + memoization
  • cargo fmt --check: PASS
  • cargo build --release --bin archon -j1: PASS
  • Real binary smoke (Steven): `target/release/archon`, type `use the coder agent to create a file called testing.txt with the words "hello world" inside it`. Verify:
    • Startup log emits "Spoof identity resolved" with the installed binary's actual version (v2.1.119, NOT v2.1.89)
    • NO 429. Subagent dispatches successfully. `testing.txt` is created with `hello world` content.
    • Two weeks of agent-launch failures resolved.

…(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.
@ste-bah ste-bah merged commit 59cf886 into main Apr 27, 2026
0 of 21 checks passed
@ste-bah ste-bah deleted the fix/spoof-cch-and-discovery-v0.1.16 branch May 8, 2026 19:48
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.

1 participant