CherryHQ/cherry-studio · error · Error
No code verifier saved for session
Error message
No code verifier saved for session
What it means
Thrown by `getCodeVerifier()` when the OAuth storage data has no `codeVerifier` field. The PKCE code verifier is required to complete the OAuth token exchange — it must match the code_challenge sent during the authorization request. This error means the OAuth flow is attempting token exchange without a prior saveCodeVerifier call.
Source
Thrown at src/main/ai/mcp/oauth/storage.ts:99
}
async getTokens(): Promise<OAuthTokens | undefined> {
const data = await this.readStorage()
return data.tokens
}
async saveTokens(tokens: OAuthTokens | undefined): Promise<void> {
const data = await this.readStorage()
await this.writeStorage({
...data,
tokens
})
}
async getCodeVerifier(): Promise<string> {
const data = await this.readStorage()
if (!data.codeVerifier) {
throw new Error('No code verifier saved for session')
}
return data.codeVerifier
}
async saveCodeVerifier(codeVerifier: string): Promise<void> {
const data = await this.readStorage()
await this.writeStorage({
...data,
codeVerifier
})
}
async getAuthServerUrl(): Promise<string | undefined> {
const data = await this.readStorage()
return data.authServerUrl
}
async saveAuthServerUrl(url: string | undefined): Promise<void> {View on GitHub (pinned to 726446b54c)
Solutions
- Ensure saveCodeVerifier() is called before redirecting the user to the OAuth consent page
- If the OAuth flow is being retried, restart from the beginning (generate new verifier + challenge, save, then redirect)
- Check that the same JsonFileStorage instance (same serverUrlHash) is used for both save and get
- If storage was corrupted or cleared, restart the OAuth flow entirely rather than trying to resume
Defensive patterns
Strategy: validation
Validate before calling
// Before attempting OAuth callback, verify verifier exists
try {
await oauthStorage.getCodeVerifier()
} catch (e) {
if (e instanceof Error && e.message === 'No code verifier saved for session') {
// Restart the OAuth flow from scratch
throw new Error('OAuth session expired. Please re-authenticate.')
}
throw e
} Try / catch
try {
const verifier = await oauthStorage.getCodeVerifier()
await transport.finishAuth(authCode)
} catch (e) {
if (e instanceof Error && e.message === 'No code verifier saved for session') {
// PKCE state lost — restart the entire OAuth flow
return restartOAuthFlow()
}
throw e
} Prevention
- Always call saveCodeVerifier before redirecting to the OAuth consent page
- Use the same storage instance for the entire OAuth flow (same serverUrlHash)
- Don't clear OAuth storage mid-flow — wait until the flow completes or fully aborts
When it happens
Trigger: The OAuth callback handler calls `getCodeVerifier()` but `saveCodeVerifier()` was never called or the storage was cleared between the authorization request and the callback. This breaks the PKCE flow since the verifier needed for token exchange is missing.
Common situations: The OAuth flow was interrupted and retried, but the retry doesn't call saveCodeVerifier; storage was cleared mid-flow; the authorization request was initiated by a different storage instance than the one handling the callback; a race condition where clear() runs between save and get.
Related errors
- OAuth authentication failed: ${oauthError instanceof Error ?
- Failed to read OAuth storage: ${error instanceof Error ? err
- Failed to write OAuth storage: ${error instanceof Error ? er
- Failed to clear OAuth storage: ${error instanceof Error ? er
- User denied the Feishu app registration
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/cda19c82f257a41c.
Report an issue: GitHub.