Skip to content

Commit b8a3c9f

Browse files
author
gluck
committed
docs: the anthropic oauth posture, and the exit
The Fable 5.1 investigation surfaced more than a version floor. The Claude Code docs now ban OAuth token use in third-party tools outright, and since ~June the token endpoint refuses claude-cli User-Agents -- the enforcement perimeter around exactly figaro's arrangement is tightening. The decision, stated where it can be found: IF OAUTH BECOMES NON-VIABLE, FIGARO CHANGES PROVIDERS. No UA arms race, no countermeasures. The exit is already built and boring -- api_key_file (no impersonation anywhere on the path), copilot, or any openaichat endpoint. A watch table says which signs are maintenance (version-floor bumps: track dist-tags.latest, since the floor for Fable 5.1 sits above the STABLE channel and even real Claude Code on stable fails) and which three signs all mean the same instruction. The version constant's comment now points here.
1 parent b463d76 commit b8a3c9f

2 files changed

Lines changed: 82 additions & 4 deletions

File tree

‎docs/anthropic-oauth-posture.md‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# The Anthropic OAuth posture, and the exit
2+
3+
Status: standing. Revisit when a refresh starts failing or a policy notice
4+
arrives.
5+
6+
## How figaro talks to Anthropic today
7+
8+
The anthropic providers present the **Claude Code OAuth credential** (a
9+
claude.ai subscription) and identify as Claude Code itself:
10+
`User-Agent: claude-cli/<version>`, the `claude-code-20250219` beta header,
11+
and a claimed client version (`anthropicmodels.ClaudeCodeVersion`). Token
12+
mint and refresh live in **hush**, not here.
13+
14+
This works precisely because the requests are indistinguishable from the
15+
real client. It is subscription-priced inference for an agent harness, and
16+
it is the arrangement this document is honest about.
17+
18+
## What the ground looks like (September 2026)
19+
20+
Three facts, found the day Fable 5.1 started refusing us:
21+
22+
1. **Models are gated on the claimed client version.** Fable 5.1 returns
23+
400 `claude_code_version_too_old` below 2.1.251. The check is a numeric
24+
threshold, not a whitelist of real releases — and the floor sits *above*
25+
the stable channel, so even genuine Claude Code on stable fails
26+
(anthropics/claude-code#91345). Hence the rule on the constant: track
27+
`dist-tags.latest`.
28+
29+
2. **The policy line is now explicit.** The Claude Code docs were updated
30+
to ban OAuth token usage in third-party tools; `claude setup-token`
31+
credentials (`sk-ant-oat01-*`) are rejected by the Messages API
32+
(claude-code#28091, #37205). What figaro does is not a gray area in the
33+
text; it is a gray area only in enforcement.
34+
35+
3. **Enforcement is visibly tightening.** Since ~June 2026 the token
36+
endpoint (`/v1/oauth/token`) refuses `claude-cli/*` User-Agents outright
37+
(hermes-agent#48534) — an anti-abuse net aimed at exactly this pattern.
38+
That is the *refresh* path, which is hush's; figaro's turns work today
39+
because hush's refresh passes today.
40+
41+
## The decision
42+
43+
**If OAuth becomes non-viable, figaro changes providers. That is the whole
44+
contingency.** No countermeasures, no UA arms race: the moment the
45+
arrangement stops working — refreshes blocked, version gates that cannot be
46+
cleared, account action — we switch credentials, not disguises.
47+
48+
The exit is already built and deliberately boring:
49+
50+
- **A plain API key**, via `api_key_file` (`auth.FileValue`): first in the
51+
resolution chain, no impersonation anywhere on the path, and the shape a
52+
server deployment uses anyway (sops → `/run/secrets` → path). Metered
53+
pricing instead of subscription; that is the cost of the exit, and it is
54+
a price question rather than an architecture question.
55+
- **Copilot**, already a first-class provider, carrying most of the same
56+
models under its own sanctioned agent quota.
57+
- **Any OpenAI-compatible endpoint** through the openaichat provider.
58+
59+
## What to watch, and what each sign means
60+
61+
| sign | meaning | response |
62+
|---|---|---|
63+
| 400 `claude_code_version_too_old` | routine version-floor bump | bump `ClaudeCodeVersion` to npm `latest` |
64+
| refreshes failing / 429 from `/v1/oauth/token` | the UA net caught the refresh path | check hush's UA on that endpoint; if intentional targeting, take the exit |
65+
| 401s with a valid, fresh token | credential class revoked | take the exit |
66+
| policy notice on the account | the question has been asked directly | take the exit |
67+
68+
The first row is maintenance. The other three are the same instruction.

‎internal/provider/anthropicmodels/anthropicmodels.go‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,18 @@ func (c *Catalog) ContextLimit(model string, snapshot form.Snapshot) int {
110110
// ClaudeCodeVersion is the client version figaro presents to the Anthropic
111111
// API. It lives HERE, once, because it used to be two constants in two
112112
// providers -- and the API gates newer models on it: Fable 5.1 refuses any
113-
// client below 2.1.251 with a 400 telling you to run `claude update`. A
114-
// stale copy in one provider is an error that only appears for whichever
115-
// models shipped after it, which reads as "figaro cannot do fable" when the
116-
// truth is "one string is old".
113+
// client below 2.1.251 with a 400 (claude_code_version_too_old) telling you
114+
// to run `claude update`. A stale copy in one provider is an error that
115+
// only appears for whichever models shipped after it, which reads as
116+
// "figaro cannot do fable" when the truth is "one string is old".
117+
//
118+
// TRACK `latest`, NOT `stable`. The floor for Fable 5.1 (2.1.251) is ABOVE
119+
// the stable channel (2.1.236 at the time): real Claude Code on stable
120+
// fails identically (anthropics/claude-code#91345). The check is a numeric
121+
// threshold, not a whitelist of released versions, so the rule when a new
122+
// model 400s is: `npm view @anthropic-ai/claude-code dist-tags.latest`,
123+
// paste it here.
124+
//
125+
// See docs/anthropic-oauth-posture.md for how long this arrangement can be
126+
// expected to hold at all.
117127
const ClaudeCodeVersion = "2.1.267"

0 commit comments

Comments
 (0)