musistudio/claude-code-router · error
ARCHIVE_ACCESS_DENIED
ARCHIVE_ACCESS_DENIED
Error message
The archive session token is invalid.
What it means
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.
Source
Thrown at packages/core/src/gateway/context-archive.ts:232
const task = input.task.trim();
const toolName = config.toolName || defaultToolName;
if (!archiveId || !sessionToken || !task) {
throw contextArchiveError("ARCHIVE_INVALID_ARGUMENT", `${toolName} requires archive_id, session_token, and task.`);
}
const store = this.store(config);
const rootSnapshot = store.get(archiveId);
if (!rootSnapshot) {
throw contextArchiveError("ARCHIVE_NOT_FOUND", `Archive ${archiveId} does not exist or has expired.`);
}
if (rootSnapshot.expiresAt !== undefined && rootSnapshot.expiresAt <= Date.now()) {
throw contextArchiveError("ARCHIVE_EXPIRED", `Archive ${archiveId} has expired.`);
}
if (rootSnapshot.status !== "ready") {
throw contextArchiveError("ARCHIVE_NOT_READY", `Archive ${archiveId} is ${rootSnapshot.status}.`);
}
if (!constantTimeEqual(rootSnapshot.tokenHash, sha256(sessionToken))) {
throw contextArchiveError("ARCHIVE_ACCESS_DENIED", "The archive session token is invalid.");
}
if (!executor) {
throw contextArchiveError("ARCHIVE_REPLAY_UNAVAILABLE", "The gateway replay executor is not available.");
}
const lineage = store.lineage(archiveId, maxLineageReplayDepth);
const searchedGenerations: number[] = [];
let lastInsufficientAnswer: { answer: string; snapshot: ArchiveSnapshot } | undefined;
for (const snapshot of lineage) {
if (snapshot.expiresAt !== undefined && snapshot.expiresAt <= Date.now()) {
continue;
}
if (snapshot.status !== "ready") {
continue;
}
const answer = await replayArchiveSnapshot(snapshot, task, config, executor);
searchedGenerations.push(snapshot.generation);
if (isInsufficientArchiveAnswer(answer) && snapshot.parentArchiveId) {View on GitHub (pinned to 99f24806c6)
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
Example fix
// before
await archive.ask(archiveId, process.env.ARCHIVE_TOKEN!, task); // stale token
// after
const { archiveId, sessionToken } = await archive.create(request);
fs.writeFileSync(tokenPath, sessionToken, 'utf8'); // persist verbatim
await archive.ask(archiveId, fs.readFileSync(tokenPath, 'utf8'), task); Defensive patterns
Strategy: validation
Validate before calling
import { createHash } from 'node:crypto';
const snap = store.get(archiveId);
if (!snap || snap.tokenHash !== createHash('sha256').update(sessionToken).digest('hex')) {
throw new Error('token does not match archive — reissue');
} Type guard
function hasValidTokenBinding(snap: { tokenHash: string } | undefined, token: string): snap is { tokenHash: string } {
return !!snap && snap.tokenHash === createHash('sha256').update(token).digest('hex');
} Try / catch
try { await archive.ask(id, token, task); }
catch (e) { if (e.code === 'ARCHIVE_ACCESS_DENIED') { /* recreate archive for a fresh token */ } throw e; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- New API refresh response did not include an access token.
- Artifact URL contains an invalid access token.
- OpenCode CLI API key was not found.
- Refusing to archive unknown legacy JSON config file: ${unsup
- Local agent account credential was not found. Sign in again,
AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27).
Data as JSON: /api/errors/2114aac6dfbdc602.
Report an issue: GitHub.