Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/core/src/cachekeep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ export class CacheKeepManager {
prepareHeaders?: (
headers: Headers,
target: CacheKeepTarget,
) => Promise<Headers> | Headers
) => Promise<Headers | undefined> | Headers | undefined
onTrackedSessionsChanged?: (
sessions: readonly CacheKeepTrackedSession[],
) => Promise<void> | void
Expand Down Expand Up @@ -662,6 +662,13 @@ export class CacheKeepManager {
prewarmTarget,
)
: new Headers(target.headers)
if (!headers) {
return {
ok: false,
reason: 'OAuth cache prewarm credential is unavailable',
transient: true,
}
}
headers.delete('content-length')
headers.delete('transfer-encoding')
let response: Response
Expand Down
17 changes: 9 additions & 8 deletions packages/core/src/prime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,9 @@ export type PrimeSendResult =
status?: number
ms?: number
error: string
// Discriminates the failure kind so the manager can emit the
// spec Logging table's two distinct warn events: `prime token
// refresh failed` (warn · prime · { account, error }) for
// token-refresh failures, `prime fire failed` (warn · prime ·
// { account, status?, error }) for HTTP / fetch / identity
// failures during the request itself.
reason?: 'token-refresh' | 'send'
// `vault-cold` is an expected off-path cache warm state, not evidence
// of a failed provider request, so it must not emit the fire-failure warn.
reason?: 'token-refresh' | 'vault-cold' | 'send'
}

/**
Expand Down Expand Up @@ -946,7 +942,7 @@ export class PrimeManager {
if (this.stopped) return

if (!result.ok) {
// Spec Logging table: two distinct warn events.
// `vault-cold` is a cache-warm skip; it is not a provider failure.
// - `prime token refresh failed` — token-refresh failure
// before the request fires (reason: 'token-refresh').
// - `prime fire failed` — HTTP error / fetch throw /
Expand All @@ -956,6 +952,11 @@ export class PrimeManager {
account: evaluation.label,
error: result.error,
})
} else if (result.reason === 'vault-cold') {
logger.debug('prime', 'prime vault credential cold', {
account: evaluation.label,
reason: result.reason,
})
} else {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
logger.warn('prime', 'prime fire failed', {
account: evaluation.label,
Expand Down
241 changes: 157 additions & 84 deletions packages/opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,8 +1366,17 @@ const anthropicAuthPlugin = async (

for (const account of storage.accounts) {
if (signal?.aborted) break
if (!isOAuthAccount(account) || !account.access) continue
const accessToken = account.access
if (!isOAuthAccount(account)) continue
const vaultEnabled =
Boolean(account.claustrumHandle) &&
isClaustrumEnabledForAccount(storage, account.id) &&
!claustrumBlockedAccounts.has(account.id)
const resolved = vaultEnabled
? resolveClaustrumAccess(account, storage)
: undefined
if (vaultEnabled && (!resolved?.accessToken || !resolved.served)) continue
const accessToken = resolved?.accessToken ?? account.access
if (!accessToken) continue
if (
account.profile &&
!oauthProfileMatchesIdentity(account.profile, account.id)
Expand Down Expand Up @@ -1928,6 +1937,65 @@ const anthropicAuthPlugin = async (
}
}

async function reportCapturedClaustrumAuthFailure(
served: {
accountId: string
handle: string
recordVersion: number
},
reporterSource: ClaustrumReporterSource = 'direct',
options?: { preserveServedVersion?: boolean },
): Promise<void> {
const cache = claustrumCredentialCache
if (!cache) return
if (
served.recordVersion <=
(claustrumLastReportedVersion.get(served.handle) ?? -1)
) {
return
}
if (!options?.preserveServedVersion) {
const current = cache.peek(served.handle)
// Version match makes reports single-shot per served version. Accepted
// tradeoff: an unrelated cache eviction also suppresses a genuine
// report (worst case one delayed cycle until the next served 401).
if (!current || current.recordVersion !== served.recordVersion) return
}
const key = `${served.handle}\0${served.recordVersion}`
const pending = claustrumAuthFailureReports.get(key)
if (pending) {
await pending
return
}
const report = (async () => {
try {
await cache.reportAuthFailure(
served.handle,
401,
{
recordVersion: served.recordVersion,
},
reporterSource,
)
claustrumLastReportedVersion.set(served.handle, served.recordVersion)
} catch (error) {
handleClaustrumCredentialError(served.accountId, error, served.handle)
logger.warn('claustrum', 'failed to report credential failure', {
accountId: served.accountId,
error: error instanceof Error ? error.message : String(error),
})
}
})()
claustrumAuthFailureReports.set(key, report)
try {
await report
} finally {
if (claustrumAuthFailureReports.get(key) === report) {
claustrumAuthFailureReports.delete(key)
}
}
}

async function ensureClaustrumCredentialCache(): Promise<ClaustrumCredentialCache | null> {
if (claustrumCredentialCache) return claustrumCredentialCache
const now = claustrumNow()
Expand Down Expand Up @@ -2318,6 +2386,10 @@ const anthropicAuthPlugin = async (
let aggregateCacheKeepSessions: ReturnType<
CacheKeepManager['trackedSessions']
> = []
const cacheKeepServedClaustrumCredentials = new Map<
string,
ClaustrumAccessResolution['served']
>()
const cacheKeepManager = new CacheKeepManager({
loadStorage: () => loadAccounts(accountStoragePath),
setIntervalImpl: runtimeTimers.setInterval,
Expand Down Expand Up @@ -2359,7 +2431,14 @@ const anthropicAuthPlugin = async (
return bodyText
}
},
onResponse: ({ target, bodyText, status, data, receivedAt }) => {
onResponse: async ({ target, bodyText, status, data, receivedAt }) => {
const served = cacheKeepServedClaustrumCredentials.get(target.id)
cacheKeepServedClaustrumCredentials.delete(target.id)
if (status === 401 && served) {
await reportCapturedClaustrumAuthFailure(served, 'direct', {
preserveServedVersion: true,
})
}
const prepared = cacheKeepDiagnosticsRequests.get(target.id)
if (!prepared?.betasHash || !prepared.betas) {
cacheKeepDiagnosticsRequests.delete(target.id)
Expand Down Expand Up @@ -2404,6 +2483,7 @@ const anthropicAuthPlugin = async (
},
prepareHeaders: async (headers, target) => {
let accessToken: string | undefined
let servedClaustrumCredential: ClaustrumAccessResolution['served']
const accountId = target.oauthAccountId
if (accountId && accountId !== 'main') {
const storage = await loadAccounts(accountStoragePath)
Expand All @@ -2418,16 +2498,29 @@ const anthropicAuthPlugin = async (
`OAuth account ${accountId} is unavailable for cache prewarm`,
)
}
let current = account
try {
current = await fallbackManager.refreshAccount(account, storage)
} catch (error) {
logger.warn('cachekeep', 'fallback token refresh failed', {
accountId,
error: error instanceof Error ? error.message : String(error),
const vaultEnabled =
Boolean(account.claustrumHandle) &&
isClaustrumEnabledForAccount(storage, account.id) &&
!claustrumBlockedAccounts.has(account.id)
if (vaultEnabled) {
const resolved = resolveClaustrumAccess(account, storage, {
warm: false,
})
if (!resolved.accessToken || !resolved.served) return undefined
accessToken = resolved.accessToken
servedClaustrumCredential = resolved.served
} else {
let current = account
try {
current = await fallbackManager.refreshAccount(account, storage)
} catch (error) {
logger.warn('cachekeep', 'fallback token refresh failed', {
accountId,
error: error instanceof Error ? error.message : String(error),
})
}
accessToken = current.access
}
accessToken = current.access
if (!accessToken) {
throw new Error(
`OAuth account ${accountId} has no access token for cache prewarm`,
Expand Down Expand Up @@ -2475,6 +2568,12 @@ const anthropicAuthPlugin = async (
} catch {
setOAuthHeaders(headers, accessToken)
}
if (servedClaustrumCredential) {
cacheKeepServedClaustrumCredentials.set(
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
target.id,
servedClaustrumCredential,
)
}
return headers
},
})
Expand Down Expand Up @@ -2564,6 +2663,7 @@ const anthropicAuthPlugin = async (
const start = performance.now()
let accessToken: string | undefined
let resolvedModel: string | undefined
let servedClaustrumCredential: ClaustrumAccessResolution['served']
try {
if (accountId === 'main') {
// Use the same refresh path the fresh-check uses, so a missing or
Expand Down Expand Up @@ -2598,22 +2698,41 @@ const anthropicAuthPlugin = async (
error: `prime: OAuth account ${accountId} is unavailable`,
}
}
let current = account
try {
// R2: the fire path refreshes ONLY the token, not the quota.
// The fresh-check already performed the single usage-API
// call and persisted the result; the fire path reuses the
// in-memory quota via the headers / URL contract. This keeps
// the cycle at exactly one quota API call per account.
current = await fallbackManager.refreshAccount(account, storage)
} catch (error) {
return {
ok: false,
reason: 'token-refresh',
error: error instanceof Error ? error.message : String(error),
const vaultEnabled =
Boolean(account.claustrumHandle) &&
isClaustrumEnabledForAccount(storage, account.id) &&
!claustrumBlockedAccounts.has(account.id)
if (vaultEnabled) {
const resolved = resolveClaustrumAccess(account, storage, {
warm: false,
})
if (!resolved.accessToken || !resolved.served) {
return {
ok: false,
reason: 'vault-cold',
error: 'prime: vault credential is unavailable',
}
}
accessToken = resolved.accessToken
servedClaustrumCredential = resolved.served
} else {
let current = account
try {
// R2: the fire path refreshes ONLY the token, not the quota.
// The fresh-check already performed the single usage-API
// call and persisted the result; the fire path reuses the
// in-memory quota via the headers / URL contract. This keeps
// the cycle at exactly one quota API call per account.
current = await fallbackManager.refreshAccount(account, storage)
} catch (error) {
return {
ok: false,
reason: 'token-refresh',
error: error instanceof Error ? error.message : String(error),
}
}
accessToken = current.access
}
accessToken = current.access
resolvedModel = CLAUDE_HAIKU_4_5_MODEL_ID
}

Expand Down Expand Up @@ -2657,6 +2776,13 @@ const anthropicAuthPlugin = async (
if (!response.ok) {
const reason =
(await response.text().catch(() => '')) || `HTTP ${response.status}`
if (response.status === 401 && servedClaustrumCredential) {
await reportCapturedClaustrumAuthFailure(
servedClaustrumCredential,
'direct',
{ preserveServedVersion: true },
)
}
return { ok: false, status: response.status, ms, error: reason }
}
const data = (await response.json().catch(() => null)) as Record<
Expand Down Expand Up @@ -5326,66 +5452,13 @@ const anthropicAuthPlugin = async (
recordVersion: number
},
reporterSource: ClaustrumReporterSource = 'direct',
options?: { preserveServedVersion?: boolean },
): Promise<void> {
const cache = claustrumCredentialCache
if (!cache) return
if (
served.recordVersion <=
(claustrumLastReportedVersion.get(served.handle) ?? -1)
) {
return
}
const current = cache.peek(served.handle)
// Version match makes reports single-shot per served version. Accepted
// tradeoff: an unrelated cache eviction also suppresses a genuine
// report (worst case one delayed cycle until the next served 401).
if (!current || current.recordVersion !== served.recordVersion)
return
const key = `${served.handle}\0${served.recordVersion}`
const pending = claustrumAuthFailureReports.get(key)
if (pending) {
await pending
return
}
const report = (async () => {
try {
await cache.reportAuthFailure(
served.handle,
401,
{
recordVersion: served.recordVersion,
},
reporterSource,
)
claustrumLastReportedVersion.set(
served.handle,
served.recordVersion,
)
} catch (error) {
handleClaustrumCredentialError(
served.accountId,
error,
served.handle,
)
logger.warn(
'claustrum',
'failed to report credential failure',
{
accountId: served.accountId,
error:
error instanceof Error ? error.message : String(error),
},
)
}
})()
claustrumAuthFailureReports.set(key, report)
try {
await report
} finally {
if (claustrumAuthFailureReports.get(key) === report) {
claustrumAuthFailureReports.delete(key)
}
}
return reportCapturedClaustrumAuthFailure(
served,
reporterSource,
options,
)
}

async function sendWithAccessToken(
Expand Down
Loading
Loading