{"record":{"id":"70095a2cbbcec4fc","repo":"can1357/oh-my-pi","slug":"unable-to-read-claude-session-info-id-detail","errorCode":null,"errorMessage":"Unable to read Claude session ${info.id}: ${detail}","messagePattern":"Unable to read Claude session (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/claude-session-store.ts","lineNumber":356,"sourceCode":"\t\t\t} catch {\n\t\t\t\t// Files may disappear while Claude rotates its session store.\n\t\t\t}\n\t\t}\n\t\treturn sessions.sort(\n\t\t\t(left, right) => right.modified.getTime() - left.modified.getTime() || left.path.localeCompare(right.path),\n\t\t);\n\t}\n\n\t/** Loads and converts a Claude transcript while preserving its source tree and timestamps. */\n\tasync load(info: ForeignSessionInfo): Promise<SessionManager> {\n\t\tif (info.source !== this.source) throw new Error(`Cannot load ${info.source} session with ClaudeSessionStore`);\n\t\tlet records: ForeignJsonRecord[];\n\t\tlet stats: fsTypes.Stats;\n\t\ttry {\n\t\t\t[records, stats] = await Promise.all([collectForeignJsonRecords(info.path), fs.stat(info.path)]);\n\t\t} catch (error) {\n\t\t\tconst detail = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new Error(`Unable to read Claude session ${info.id}: ${detail}`);\n\t\t}\n\t\tif (records.length === 0 && stats.size > 0)\n\t\t\tthrow new Error(`Claude session ${info.id} contains no readable records`);\n\n\t\tconst sourceParents = new Map<string, string | null>();\n\t\tlet sourceCwd: string | undefined;\n\t\tlet sourceTitle: string | undefined;\n\t\tlet aiTitle: string | undefined;\n\t\tfor (const { value } of records) {\n\t\t\tconst uuid = stringField(value, \"uuid\");\n\t\t\tif (uuid) sourceParents.set(uuid, stringField(value, \"parentUuid\") ?? null);\n\t\t\tif (!sourceCwd) sourceCwd = stringField(value, \"cwd\");\n\t\t\tif (value.type === \"custom-title\") sourceTitle = stringField(value, \"customTitle\") ?? sourceTitle;\n\t\t\tif (value.type === \"ai-title\") aiTitle = stringField(value, \"aiTitle\") ?? aiTitle;\n\t\t}\n\n\t\tconst manager = SessionManager.inMemory(sourceCwd ?? info.cwd);\n\t\tconst sourceTails = new Map<string, string>();","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/claude-session-store.ts#L338-L374","documentation":"ClaudeSessionStore.load() reads a Claude Code session file (JSONL) from disk and converts it into an OMP session. This error wraps any failure from the underlying record-collection or stat calls — file missing, permission denied, corrupted/unparseable JSON lines — embedding the original message as `detail`. It exists so callers get a uniform error naming the session id regardless of which low-level read failed.","triggerScenarios":"Calling `claudeSessionStore.load(info)` where info.path points to a file that does not exist, is unreadable (permissions), is deleted between listing and load, or whose contents make collectForeignJsonRecords/fs.stat throw.","commonSituations":"Claude Code updated its session storage location or format so an old/stale entry in a session list no longer resolves; resuming a session after the ~/.claude/projects directory was cleaned or moved; running under a different user without read permission on the JSONL file.","solutions":["Verify the file at info.path exists and is readable (ls -l / cat the JSONL path shown in the error)","Re-list sessions via the store's list() so info comes from a fresh scan rather than a cached/old entry","Reinstall or update Claude Code if its session format/location changed, then re-import the session","Recreate the session if the underlying transcript file is gone — it cannot be loaded"],"exampleFix":"// before\nconst session = await store.load(staleInfo); // staleInfo from an old listing\n// after\nconst sessions = await store.list();\nconst info = sessions.find(s => s.id === wantedId);\nif (!info) throw new Error('session no longer available');\nconst session = await store.load(info);","handlingStrategy":"try-catch","validationCode":"import * as fs from \"node:fs\";\n// before load:\n// const stat = fs.statSync(info.path); // throws ENOENT/EACCES early\n// if (stat.size === 0) throw new Error(`empty session file: ${info.path}`);","typeGuard":"function isMissingFileError(err: unknown): boolean {\n  return err instanceof Error && (err as NodeJS.ErrnoException).code === 'ENOENT';\n}","tryCatchPattern":"try {\n  const session = await store.load(info);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Unable to read Claude session')) {\n    logger.warn('claude session unreadable, skipping', { id: info.id, cause: err.message });\n    return null; // degrade gracefully in session lists\n  }\n  throw err;\n}","preventionTips":["Always source ForeignSessionInfo from a fresh store.list() call, never cached entries","Stat the file before load when operating on user-provided paths","Handle ENOENT as an expected case (file deleted since listing) in session-picker UIs","Keep Claude Code and OMP versions in sync to avoid format drift"],"tags":["session","filesystem","io","claude"],"backgroundTag":"session-file-unreadable","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}