{"record":{"id":"2794cbc59ddf5585","repo":"musistudio/claude-code-router","slug":"archive-expired","errorCode":"ARCHIVE_EXPIRED","errorMessage":"Archive ${archiveId} has expired.","messagePattern":"Archive (.+?) has expired\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/gateway/context-archive.ts","lineNumber":226,"sourceCode":"    archiveId: string;\n    sessionToken: string;\n    task: string;\n  }, config: ContextArchiveConfig, executor?: ContextArchiveReplayExecutor): Promise<ContextArchiveAskOutput> {\n    const archiveId = input.archiveId.trim();\n    const sessionToken = input.sessionToken.trim();\n    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      }","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/gateway/context-archive.ts#L208-L244","documentation":"Thrown by the context archive gateway when replaying an archive whose snapshot expiry timestamp has passed. The archive store keeps snapshots with an optional expiresAt; once Date.now() exceeds it, the snapshot is treated as gone even if the row still exists. It protects stale context from being replayed.","triggerScenarios":"Calling ask(archiveId, sessionToken, ...) after the archive's TTL elapsed — e.g. a job resumed hours later, or the archive was created with a short retention window. The snapshot exists and status is ready, but expiresAt <= Date.now().","commonSituations":"Long-running pipelines that pause between turns; retention/TTL config set too low for the workflow's wall-clock duration; clock skew between writer and reader.","solutions":["Re-create the archive (a new root snapshot) and restart the replay session with the new archiveId","Increase the archive TTL/retention configuration when creating snapshots","Complete replay workflows within the configured expiry window"],"exampleFix":"// before\nconst answer = await archive.ask(archiveId, token, task); // ARCHIVE_EXPIRED\n\n// after\nconst snap = archive.inspect(archiveId);\nif (snap?.expiresAt && snap.expiresAt <= Date.now()) {\n  const fresh = await archive.create(/* new root snapshot */);\n  archiveId = fresh.archiveId; token = fresh.sessionToken;\n}\nconst answer = await archive.ask(archiveId, token, task);","handlingStrategy":"validation","validationCode":"const snap = store.get(archiveId);\nif (!snap || (snap.expiresAt !== undefined && snap.expiresAt <= Date.now() + 60_000)) {\n  throw new Error('archive expired or expiring imminently — recreate it');\n}\nawait archive.ask(archiveId, sessionToken, task);","typeGuard":"function isUsableArchive(s: { status: string; expiresAt?: number } | undefined): s is { status: 'ready'; expiresAt?: number } {\n  return !!s && s.status === 'ready' && (s.expiresAt === undefined || s.expiresAt > Date.now());\n}","tryCatchPattern":"try { await archive.ask(id, token, task); }\ncatch (e) { if (e.code === 'ARCHIVE_EXPIRED') { /* recreate archive, restart session */ } throw e; }","preventionTips":["Set archive TTL comfortably larger than the maximum wall-clock span of your workflow","Check expiresAt with a safety margin before every replay","Persist archiveId+token pairs together with their creation time so expiry is predictable"],"tags":["archive","expiration","gateway","context"],"backgroundTag":"session-expired","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}