{"record":{"id":"2114aac6dfbdc602","repo":"musistudio/claude-code-router","slug":"archive-access-denied","errorCode":"ARCHIVE_ACCESS_DENIED","errorMessage":"The archive session token is invalid.","messagePattern":"The archive session token is invalid\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/gateway/context-archive.ts","lineNumber":232,"sourceCode":"    const task = input.task.trim();\n    const toolName = config.toolName || defaultToolName;\n    if (!archiveId || !sessionToken || !task) {\n      throw contextArchiveError(\"ARCHIVE_INVALID_ARGUMENT\", `${toolName} requires archive_id, session_token, and task.`);\n    }\n\n    const store = this.store(config);\n    const rootSnapshot = store.get(archiveId);\n    if (!rootSnapshot) {\n      throw contextArchiveError(\"ARCHIVE_NOT_FOUND\", `Archive ${archiveId} does not exist or has expired.`);\n    }\n    if (rootSnapshot.expiresAt !== undefined && rootSnapshot.expiresAt <= Date.now()) {\n      throw contextArchiveError(\"ARCHIVE_EXPIRED\", `Archive ${archiveId} has expired.`);\n    }\n    if (rootSnapshot.status !== \"ready\") {\n      throw contextArchiveError(\"ARCHIVE_NOT_READY\", `Archive ${archiveId} is ${rootSnapshot.status}.`);\n    }\n    if (!constantTimeEqual(rootSnapshot.tokenHash, sha256(sessionToken))) {\n      throw contextArchiveError(\"ARCHIVE_ACCESS_DENIED\", \"The archive session token is invalid.\");\n    }\n    if (!executor) {\n      throw contextArchiveError(\"ARCHIVE_REPLAY_UNAVAILABLE\", \"The gateway replay executor is not available.\");\n    }\n\n    const lineage = store.lineage(archiveId, maxLineageReplayDepth);\n    const searchedGenerations: number[] = [];\n    let lastInsufficientAnswer: { answer: string; snapshot: ArchiveSnapshot } | undefined;\n    for (const snapshot of lineage) {\n      if (snapshot.expiresAt !== undefined && snapshot.expiresAt <= Date.now()) {\n        continue;\n      }\n      if (snapshot.status !== \"ready\") {\n        continue;\n      }\n      const answer = await replayArchiveSnapshot(snapshot, task, config, executor);\n      searchedGenerations.push(snapshot.generation);\n      if (isInsufficientArchiveAnswer(answer) && snapshot.parentArchiveId) {","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/gateway/context-archive.ts#L214-L250","documentation":"Thrown when sha256(sessionToken) does not match the tokenHash stored on the snapshot, compared with constantTimeEqual. Each archive is bound to a one-time session token issued at creation; replay requires presenting exactly that token.","triggerScenarios":"Passing a truncated, regenerated, or wrong-session token to ask(); reusing a token from a different archiveId; token mangled by encoding/URL-decoding or copy-paste errors.","commonSituations":"Tokens persisted incorrectly (escaped/quoted) across process restarts; multi-worker setups where the token was issued on another instance; mixing tokens between concurrently created archives.","solutions":["Use the exact sessionToken returned when the archive was created — store and pass it byte-for-byte","Verify you are pairing the right token with the right archiveId","If the original token is lost, create a new archive to obtain a fresh token"],"exampleFix":"// before\nawait archive.ask(archiveId, process.env.ARCHIVE_TOKEN!, task); // stale token\n\n// after\nconst { archiveId, sessionToken } = await archive.create(request);\nfs.writeFileSync(tokenPath, sessionToken, 'utf8'); // persist verbatim\nawait archive.ask(archiveId, fs.readFileSync(tokenPath, 'utf8'), task);","handlingStrategy":"validation","validationCode":"import { createHash } from 'node:crypto';\nconst snap = store.get(archiveId);\nif (!snap || snap.tokenHash !== createHash('sha256').update(sessionToken).digest('hex')) {\n  throw new Error('token does not match archive — reissue');\n}","typeGuard":"function hasValidTokenBinding(snap: { tokenHash: string } | undefined, token: string): snap is { tokenHash: string } {\n  return !!snap && snap.tokenHash === createHash('sha256').update(token).digest('hex');\n}","tryCatchPattern":"try { await archive.ask(id, token, task); }\ncatch (e) { if (e.code === 'ARCHIVE_ACCESS_DENIED') { /* recreate archive for a fresh token */ } throw e; }","preventionTips":["Store archiveId and sessionToken as an atomic pair; never mix them","Pass tokens verbatim — no trimming, decoding, or re-encoding","Never log or persist tokens in URLs where they get transformed"],"tags":["archive","auth","token","timing-safe"],"backgroundTag":"invalid-session-token","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}