{"record":{"id":"cda19c82f257a41c","repo":"CherryHQ/cherry-studio","slug":"no-code-verifier-saved-for-session","errorCode":null,"errorMessage":"No code verifier saved for session","messagePattern":"No code verifier saved for session","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/oauth/storage.ts","lineNumber":99,"sourceCode":"  }\n\n  async getTokens(): Promise<OAuthTokens | undefined> {\n    const data = await this.readStorage()\n    return data.tokens\n  }\n\n  async saveTokens(tokens: OAuthTokens | undefined): Promise<void> {\n    const data = await this.readStorage()\n    await this.writeStorage({\n      ...data,\n      tokens\n    })\n  }\n\n  async getCodeVerifier(): Promise<string> {\n    const data = await this.readStorage()\n    if (!data.codeVerifier) {\n      throw new Error('No code verifier saved for session')\n    }\n    return data.codeVerifier\n  }\n\n  async saveCodeVerifier(codeVerifier: string): Promise<void> {\n    const data = await this.readStorage()\n    await this.writeStorage({\n      ...data,\n      codeVerifier\n    })\n  }\n\n  async getAuthServerUrl(): Promise<string | undefined> {\n    const data = await this.readStorage()\n    return data.authServerUrl\n  }\n\n  async saveAuthServerUrl(url: string | undefined): Promise<void> {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/oauth/storage.ts#L81-L117","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before attempting OAuth callback, verify verifier exists\ntry {\n  await oauthStorage.getCodeVerifier()\n} catch (e) {\n  if (e instanceof Error && e.message === 'No code verifier saved for session') {\n    // Restart the OAuth flow from scratch\n    throw new Error('OAuth session expired. Please re-authenticate.')\n  }\n  throw e\n}","typeGuard":null,"tryCatchPattern":"try {\n  const verifier = await oauthStorage.getCodeVerifier()\n  await transport.finishAuth(authCode)\n} catch (e) {\n  if (e instanceof Error && e.message === 'No code verifier saved for session') {\n    // PKCE state lost — restart the entire OAuth flow\n    return restartOAuthFlow()\n  }\n  throw e\n}","preventionTips":["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"],"tags":["mcp","oauth","pkce","storage","authentication"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}