Skip to content

fix: prewarm and profile hydration use the vault credential for a vault-served fallback - #199

Open
iceteaSA wants to merge 4 commits into
cortexkit:mainfrom
iceteaSA:fix/prewarm-profile-vault-token
Open

fix: prewarm and profile hydration use the vault credential for a vault-served fallback#199
iceteaSA wants to merge 4 commits into
cortexkit:mainfrom
iceteaSA:fix/prewarm-profile-vault-token

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Stacked on #197 (prime). Same defect class, two more sites.

Bug

A vault-served fallback's account.access is the frozen sidecar token; the vault's first rotation revokes it. Two paths still sent it:

  • CacheKeep prewarm (prepareHeaders, fallback branch): refreshAccount is a deliberate no-op for a vault-served account, then accessToken = current.access. Every 1h-cache keepalive on the custodied fallback 401'd silently, so its cache entries were never extended. The Fable/Opus recovery source-model prewarm and the Claude Code bootstrap call ride the same provider and inherited the wrong token.
  • Profile hydration: const accessToken = account.accessfetchOAuthAccountProfile. Tier never refreshed for a vault-served fallback; the 401 was swallowed.

The quota poll (v1.22.0) and prime (#197) were the first two instances. A census of every outbound use of a fallback token found these as the remaining two.

Fix

Both sites resolve through the same custody resolver prime uses:

  • vault-enabled with a usable resident credential → vault token (the bootstrap receives it too);
  • vault-enabled but cold → CacheKeep skips in its existing transient shape ({ ok: false, transient: true }: target retained, cacheExpiresAt untouched, bounded retry — the fix(core): contain thrown prewarm errors in the cachekeep tick #155 fetch-timeout shape); profile hydration skips and retries next tick;
  • vault disabled → sidecar behaviour unchanged.

A 401 on a vault-token prewarm reports report_auth_failure once with the record_version the prewarm was sent with (captured at send; a re-peek after a concurrent rotation would name a superseded version), through the same (handle, version) dedup as prime. A sidecar-token 401 never reports.

Census test

fallback-token-use-sites.test.ts: one it per outbound site — request path (+ lane start + request bootstrap), quota poll, prime, CacheKeep prewarm (+ prewarm bootstrap), recovery source-model prewarm, profile hydration. Each configures a vault-served fallback with sidecar sk-ant-oat01-sidecar-canary-<site> and resident sk-ant-oat01-vault-<site>, drives the real path, and asserts every captured Authorization is the vault token and the canary appears in no request, with a positive control on request count. Reintroducing accessToken = current.access in prepareHeaders reds exactly the two sites that share it (cachekeep, recovery); the other four stay green. A fifth instance of this class now names its site.

Verification

Production mutations red first: cachekeep current.access; profile account.access; cold vault falling through to sidecar; provenance fence dropped (0 reports); re-peek at report time after a rotation (report lost). Reviewer (MiniMax M3) re-ran all five independently: APPROVE 0/0.

Gates: core build, root bun run test 169/0 core + 1566/0 opencode, typecheck, format:check, biome.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes CacheKeep prewarm and profile hydration sending the frozen sidecar token for vault-served fallbacks, so the vault-issued credential is used and 401s are reported correctly.

Behavior

  • Prewarm and profile hydration now resolve the vault credential through the same custody resolver prime uses; a cold vault skips instead of sending the revoked token.
  • A vault-token 401 reports the record version captured at send time, deduped per handle/version and bound to the attempt that served it; a sidecar-token 401 never reports.
  • Prime logs a cold vault at debug and shows it as skipped instead of a fire-failed error.
  • Adds a census test asserting every outbound fallback token site sends only the vault token.

Written for commit bd6abcc. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files

Architecture diagram
sequenceDiagram
    participant Client as OpenCode Client
    participant Plugin as Anthropic Auth Plugin
    participant Resolver as Custody Resolver
    participant Vault as Vault Credential Service
    participant CacheKeep as CacheKeep Manager
    participant Provider as Anthropic API
    participant Reporter as Auth Failure Reporter
    participant Profile as Profile and Quota Hydration

    Note over Client,Provider: Runtime flow for OAuth fallback accounts with optional vault custody

    Client->>Plugin: Send authenticated request
    Plugin->>Resolver: Resolve fallback credential
    alt Vault enabled and resident credential usable
        Resolver->>Vault: Read resident credential by handle
        Vault-->>Resolver: Vault token and record_version
        Resolver-->>Plugin: Vault token with served provenance
    else Vault enabled but credential cold
        Resolver-->>Plugin: Credential unavailable
        Plugin-->>Client: Skip dependent operation and retry later
    else Vault disabled or blocked
        Resolver-->>Plugin: Stored sidecar access token
    end
    Plugin->>Provider: Request with Authorization bearer token
    Provider-->>Plugin: Response
    Plugin-->>Client: Stream or request result

    opt Quota polling
        Plugin->>Resolver: Resolve fallback credential
        Resolver-->>Plugin: Vault token or sidecar token
        Plugin->>Provider: GET OAuth usage with resolved token
        Provider-->>Plugin: Quota snapshot
    end

    opt Prime and recovery prewarm
        Plugin->>Resolver: Resolve credential for prewarm
        alt Vault credential resident
            Resolver-->>Plugin: Vault token and served record_version
            Plugin->>Provider: Prewarm messages request with vault token
            Provider-->>Plugin: Prewarm response
        else Vault credential cold
            Resolver-->>Plugin: vault-cold
            Plugin-->>Plugin: Skip request and log expected cold state
        else Vault disabled
            Resolver-->>Plugin: Refreshed sidecar token
            Plugin->>Provider: Prewarm messages request with sidecar token
            Provider-->>Plugin: Prewarm response
        end
        opt Vault-token prewarm returns 401
            Plugin->>Reporter: Report auth failure
            Reporter->>Vault: report_auth_failure
            Vault-->>Reporter: Report accepted
            Note over Plugin,Reporter: Reports are deduplicated by handle and record_version. Sidecar 401s are not reported
        end
    end

    opt CacheKeep hourly prewarm
        CacheKeep->>Plugin: prepareHeaders target
        Plugin->>Resolver: Resolve fallback credential
        alt Vault credential resident
            Resolver-->>Plugin: Vault token and served provenance
            Plugin-->>CacheKeep: Headers with vault token
            CacheKeep->>Provider: Cache prewarm with vault token
            Provider-->>CacheKeep: Prewarm response
        else Vault credential cold
            Resolver-->>Plugin: No usable credential
            Plugin-->>CacheKeep: transient unavailable result
            CacheKeep-->>CacheKeep: Retain target and cache expiry. Bounded retry
        else Vault disabled
            Resolver-->>Plugin: Refreshed sidecar token
            Plugin-->>CacheKeep: Headers with sidecar token
            CacheKeep->>Provider: Cache prewarm with sidecar token
            Provider-->>CacheKeep: Prewarm response
        end
        opt Vault-token prewarm returns 401
            CacheKeep->>Reporter: Report captured handle and record_version
            Reporter->>Vault: report_auth_failure
            Vault-->>Reporter: Report accepted
        end
    end

    opt Profile hydration
        Plugin->>Resolver: Resolve fallback credential
        alt Vault credential resident
            Resolver-->>Plugin: Vault token
            Plugin->>Profile: Fetch OAuth account profile with vault token
            Profile->>Provider: GET OAuth profile
            Provider-->>Profile: Organization and tier
            Profile-->>Plugin: Hydrated profile
        else Vault credential cold
            Resolver-->>Plugin: No usable credential
            Plugin-->>Plugin: Skip hydration and retry next tick
        else Vault disabled
            Resolver-->>Plugin: Stored sidecar token
            Plugin->>Profile: Fetch OAuth account profile with sidecar token
            Profile->>Provider: GET OAuth profile
            Provider-->>Profile: Organization and tier
            Profile-->>Plugin: Hydrated profile
        end
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/index.ts
Comment thread packages/core/src/prime.ts
Comment thread packages/opencode/src/tests/fallback-token-use-sites.test.ts Outdated
Comment thread packages/opencode/src/tests/fallback-token-use-sites.test.ts Outdated
…lear it on every exit; count a cold-vault prime as a skip

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 10 files (changes from recent commits).

Confidence score: 4/5

  • packages/opencode/src/tests/index.test.ts only checks that report_auth_failure was not called, so it could pass without proving the first prewarm used the vault token and the second used the sidecar fallback; add positive assertions for both token sources to make the test catch regressions.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/tests/index.test.ts">

<violation number="1" location="packages/opencode/src/tests/index.test.ts:2177">
P3: This test only asserts zero report_auth_failure calls; it never verifies that the first prewarm actually served the vault token or that the second fell back to the sidecar token. Add a positive control so the test would fail if both prewarms used the sidecar credential.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}
})

test('CacheKeep clears a failed vault attempt before a sidecar 401', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test only asserts zero report_auth_failure calls; it never verifies that the first prewarm actually served the vault token or that the second fell back to the sidecar token. Add a positive control so the test would fail if both prewarms used the sidecar credential.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/tests/index.test.ts, line 2177:

<comment>This test only asserts zero report_auth_failure calls; it never verifies that the first prewarm actually served the vault token or that the second fell back to the sidecar token. Add a positive control so the test would fail if both prewarms used the sidecar credential.</comment>

<file context>
@@ -2174,6 +2174,199 @@ describe('fallback Claustrum credential resolution', () => {
     }
   })
 
+  test('CacheKeep clears a failed vault attempt before a sidecar 401', async () => {
+    const calls: CredentialCall[] = []
+    const storage = fallbackWithClaustrum({
</file context>

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