{"record":{"id":"275fddd7ce6baa8e","repo":"openclaw/openclaw","slug":"claude-session-cannot-be-resumed-in-a-terminal","errorCode":null,"errorMessage":"Claude session cannot be resumed in a terminal","messagePattern":"Claude session cannot be resumed in a terminal","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/anthropic/session-catalog-node-commands.ts","lineNumber":53,"sourceCode":"    });\n    const record = page.sessions.find((candidate) => candidate.threadId === threadId);\n    if (record) {\n      if (isResumableClaudeSource(record.source)) {\n        return record;\n      }\n      break;\n    }\n    const nextCursor = page.nextCursor;\n    if (nextCursor === undefined || seenCursors.has(nextCursor)) {\n      break;\n    }\n    if (!isExactClaudeSessionCursor(nextCursor)) {\n      throw new Error(\"Claude session catalog returned an invalid cursor\");\n    }\n    seenCursors.add(nextCursor);\n    cursor = nextCursor;\n  }\n  throw new Error(\"Claude session cannot be resumed in a terminal\");\n}\n\nexport async function listClaudeSessions(paramsJSON?: string | null): Promise<string> {\n  return JSON.stringify(await listLocalClaudeSessionPage(parseNodeParams(paramsJSON)));\n}\n\nexport async function readClaudeSession(paramsJSON?: string | null): Promise<string> {\n  return JSON.stringify(await readLocalClaudeTranscriptPage(parseNodeParams(paramsJSON)));\n}\n\nexport async function resumeClaudeSession(\n  paramsJSON: string | null | undefined,\n  io: OpenClawPluginNodeHostCommandIo | undefined,\n): Promise<string> {\n  if (!io) {\n    throw new Error(\"Claude terminal command requires duplex transport\");\n  }\n  const params = decodeNodePtyResumeParams(paramsJSON, validateClaudeSessionId);","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/anthropic/session-catalog-node-commands.ts#L35-L71","documentation":"requireLocalResumableClaudeSession walks the local session catalog for a given threadId; it throws when the loop ends without returning — either the threadId was not found in any page, or it was found but isResumableClaudeSource(record.source) was false (that source type cannot be resumed in a terminal).","triggerScenarios":"(a) threadId matches no local session across all catalog pages; (b) the matching session's source type is not resumable in a terminal (e.g. a remote/non-PTY source); (c) pagination ended early because nextCursor was undefined or repeated.","commonSituations":"Resuming a session that lives on a different host; a threadId typo or stale id; the session exists only in a source the terminal resumer does not support; a local session index not yet populated.","solutions":["List local sessions (listClaudeSessions) and confirm the threadId exists on this host.","Verify the session's source is one isResumableClaudeSource accepts — only resumable sources can be terminal-resumed.","If the session is remote, resume it through its native surface instead of the terminal path.","Rebuild the local session catalog if the index is stale or empty."],"exampleFix":"// before\nconst record = await requireLocalResumableClaudeSession(params.threadId); // throws\n// after — check existence and resumability explicitly for a precise message\nconst record = await findLocalSession(params.threadId);\nif (!record) throw new Error(`Session ${params.threadId} not found locally`);\nif (!isResumableClaudeSource(record.source))\n  throw new Error(`Session ${params.threadId} source (${record.source}) is not terminal-resumable`);","handlingStrategy":"validation","validationCode":"// Confirm the session exists and is resumable before attempting terminal resume.\nconst page = await listLocalClaudeSessionPage({ limit: 100 });\nconst record = page.sessions.find(s => s.threadId === threadId);\nif (!record || !isResumableClaudeSource(record.source)) { /* not terminal-resumable */ }","typeGuard":"import { isResumableClaudeSource } from './session-catalog-shared.js';\n// isResumableClaudeSource(record.source): boolean — narrows to terminal-resumable sources","tryCatchPattern":"try { await requireLocalResumableClaudeSession(threadId); } catch (e) {\n  if (/cannot be resumed/.test(String(e.message))) { /* list sessions; suggest native resume */ }\n}","preventionTips":["List local sessions first to confirm the threadId exists on this host.","Only attempt terminal resume for sources isResumableClaudeSource accepts.","Resume remote sessions through their native surface, not the terminal path."],"tags":["session-catalog","resume","terminal","cli","not-found"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}