Skip to content

fix(claude): enable 1M context detection inside moat containers#308

Open
abezzub-dr wants to merge 4 commits into
majorcontext:mainfrom
abezzub-dr:fix/1m-context-bootstrap-cache
Open

fix(claude): enable 1M context detection inside moat containers#308
abezzub-dr wants to merge 4 commits into
majorcontext:mainfrom
abezzub-dr:fix/1m-context-bootstrap-cache

Conversation

@abezzub-dr
Copy link
Copy Markdown
Contributor

@abezzub-dr abezzub-dr commented Apr 8, 2026

Summary

  • Fix Claude Code not detecting 1M context window subscription inside moat containers
  • Three issues combined: non-OAuth-looking placeholder token, missing bootstrap response, no subscription metadata in credentials
  • Cache /api/bootstrap response at grant time (when host OAuth token is fresh) and serve it via proxy
  • Use sk-ant-oat01-* placeholder in .credentials.json instead of CLAUDE_CODE_OAUTH_TOKEN env var
  • Include subscription metadata in credentials file and synthetic profile responses

Context

Claude Code calls /api/bootstrap at startup to determine account capabilities. Setup-tokens (from claude setup-token) lack the scopes for this endpoint to return proper account info and feature flags, causing Claude Code to report "Opus 4.6 with 1M context is not available for your account."

This PR caches the full bootstrap response at grant time (when the host's short-lived OAuth token is guaranteed fresh), stores it in the encrypted credential store, and has the proxy serve it instead of the limited setup-token response. As a fallback, it also attempts to fetch bootstrap at run time if the host token is still valid.

Additionally, the CLAUDE_CODE_OAUTH_TOKEN env var (set to moat-proxy-injected) is replaced with a proper .credentials.json containing an sk-ant-oat01-* prefix placeholder, so Claude Code recognizes the session as OAuth-authenticated.

Test plan

  • Run moat grant claude --verbose and verify cached bootstrap response at grant time appears with non-zero bootstrap_len
  • Run moat claude and verify Claude Code reports 1M context availability
  • Verify moat claude still works with anthropic (API key) grants (no regression)
  • Verify unit tests pass: make test-unit
  • Verify lint passes: make lint

🤖 Generated with Claude Code

Three issues combined to prevent Claude Code from detecting 1M context
window subscription capabilities inside moat:

1. CLAUDE_CODE_OAUTH_TOKEN=moat-proxy-injected didn't look like a real
   OAuth token (sk-ant-oat01-*), so Claude Code didn't recognize it as
   an OAuth session.

2. /api/bootstrap returned account:null with setup-tokens (lacking
   scopes), so Claude Code couldn't detect subscription tier.

3. .credentials.json had no subscription metadata (subscriptionType,
   rateLimitTier).

Fix by:
- Removing CLAUDE_CODE_OAUTH_TOKEN env var; using .credentials.json with
  a proper sk-ant-oat01-* placeholder token instead
- Caching the full /api/bootstrap response at grant time (when the
  host's OAuth token is fresh) and at run time (as fallback)
- Serving the cached bootstrap via the proxy response transformer
- Including subscription metadata in .credentials.json and synthetic
  /api/oauth/profile responses

The grant-time caching is the key reliability improvement: at grant time
the user has just authenticated, so the host's short-lived OAuth token
is valid. The cached bootstrap persists in the encrypted credential
store and is available at run time even if the host token has since
expired.
Setup-token grants don't set ExpiresAt on the credential, leaving it as
the zero time.Time. WriteCredentialsFile called UnixMilli() on this,
producing -62135596800000 (year 0001) in .credentials.json. Claude Code
interpreted this as an expired credential, showing "not logged in" and
"API Usage Billing" instead of recognizing the subscription session.

Use a 1-year-ahead timestamp when ExpiresAt is zero, since setup-tokens
are long-lived and the placeholder credential just needs to look valid.
…data present

The cached bootstrap was served unconditionally, overriding even valid
responses from full-scope tokens. Now the proxy reads the real response
first: if it contains non-null account data, it passes through
untouched. The cached version is only used as a fallback when the
response is degraded (setup-token returning account:null, or non-200).

This prevents stale subscription data when tokens get refreshed, and
documents the remaining staleness limitation for setup-token grants.
…file, add tests

- Consolidate cred.Metadata nil-check to single init at top of
  cacheBootstrapForCredential instead of three conditional branches
- Replace fmt.Sprintf JSON construction with json.Marshal via typed
  oauthProfileResponse struct in createOAuthResponseWithMeta — handles
  special characters correctly and omits empty subscription fields
- Add TestCreateOAuthResponseWithMeta (5 cases) covering subscription
  metadata, omitempty, and special character handling
- Add TestOAuthEndpointTransformer_403Workarounds (3 cases) covering
  403 transformation, passthrough, and non-workaround endpoints
@dpup
Copy link
Copy Markdown
Collaborator

dpup commented Apr 8, 2026

Do you mind if we split this up. The placeholder token and expiresAt seem simple an clearly correct. I'm a little more worried about the bootstrap caching and subscription stuff.

For the second part I think we should also orient around grant-time capture, run-time synthesize. I like the current implementation has cross --profile bleed.

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.

2 participants