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>
Showing 1 changed file with 26 additions and 13 deletions.
| @@ -149,19 +149,32 @@ top of it, not a prerequisite. | ||
| 149 | 149 | |
| 150 | 150 | ## The system prompt |
| 151 | 151 | |
| 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. | |
| 165 | 178 | |
| 166 | 179 | ## Testing |
| 167 | 180 | |