Vectrum
Sign In

Design: settle whether OAuth needs the Claude Code identity block

It does. Four interleaved requests, same token, split perfectly by
shape: identity block 200, no identity block 429.

Interleaved because the first run was confounded — six plain requests
429'd over two minutes and the identity one then succeeded, which reads
just as well as a rate limit clearing. Alternating rules that out.

Recorded the status codes rather than the inference, including the part
that will waste someone's afternoon: the rejection is a 429
rate_limit_error, not a 401, so it looks exactly like a quota problem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Jjonicommitted 1 parent fdda42bcommitd33f0be

Showing 1 changed file with 26 additions and 13 deletions.

+26−13docs/superpowers/specs/2026-09-17-claude-code-oauth-design.mdView File
@@ -149,19 +149,32 @@ top of it, not a prerequisite.
149149
150150 ## The system prompt
151151
152- OAuth requests are expected to require the Claude Code identity string as the
153- first system block. If that holds, `defaultSystem` at `loop.go:26` is currently
154- the first block and these requests will be rejected.
155-
156- This is also a question rather than a fact, and a cheap one: one live request
157- with the token and no identity block. If it fails, the agent prepends a second
158- `SystemBlock` carrying the identity string when — and only when — the credential
159- came from OAuth. Both blocks stay static, so the cached prefix survives intact,
160- which is the property `defaultSystem`'s comment exists to protect. If the
161- request succeeds, nothing changes and this section is deleted.
162-
163- Ordering matters: this probe uses the *access* token and touches no refresh
164- token, so it is free of the rotation risk above and can run first.
152+ **Measured: the identity string is required.** Four requests, alternating
153+ shapes back to back against `api.anthropic.com/v1/messages` with the same
154+ token:
155+
156+ | System blocks | Result |
157+ | --- | --- |
158+ | `defaultSystem` alone | `429` ×2 |
159+ | identity string, then `defaultSystem` | `200` ×2 |
160+
161+ Interleaved deliberately, because the first run of this probe was confounded:
162+ six plain requests 429'd over two minutes and the identity request then
163+ succeeded on its first try, which is equally consistent with a rate limit
164+ clearing. Alternating removes that reading — a real rate limit does not sort
165+ itself by system prompt.
166+
167+ The rejection arrives as **`429` with `"type":"rate_limit_error"`**, not the
168+ `401` or `403` you would expect for a credential the server declines to honour.
169+ That is worth writing down: an OAuth request missing the identity block looks
170+ exactly like a quota problem, and waiting it out is the obvious wrong response.
171+
172+ So the agent prepends a second `SystemBlock` carrying the identity string when —
173+ and only when — the credential came from OAuth. Both blocks stay static, so the
174+ cached prefix survives intact, which is the property `defaultSystem`'s comment
175+ exists to protect. The cache breakpoint moves to the *last* system block; left
176+ on the first, it would cache the identity string and re-read the real prompt
177+ every turn.
165178
166179 ## Testing
167180