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

  1. Use the exact sessionToken returned when the archive was created — store and pass it byte-for-byte
  2. Verify you are pairing the right token with the right archiveId
  3. 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

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

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/2114aac6dfbdc602. Report an issue: GitHub.