{"record":{"id":"d792356d2eb6b722","repo":"can1357/oh-my-pi","slug":"claude-session-info-id-contains-no-readable-rec","errorCode":null,"errorMessage":"Claude session ${info.id} contains no readable records","messagePattern":"Claude session (.+?) contains no readable records","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/claude-session-store.ts","lineNumber":359,"sourceCode":"\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>();\n\t\tconst usedIds = new Set<string>();\n\t\tconst toolNames = new Map<string, string>();\n\t\tlet lastModel: string | undefined;","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/claude-session-store.ts#L341-L377","documentation":"ClaudeSessionStore.load() found that the session file exists and has nonzero size (stats.size > 0) but zero records could be parsed out of it by collectForeignJsonRecords. The library throws this to distinguish 'file present but entirely unparseable' from the generic read failure above.","triggerScenarios":"Calling load(info) on a Claude session JSONL whose every line fails to parse as a valid JSON record — e.g. the file is binary, truncated mid-write, empty JSON shells, or an unrecognized/rotated format.","commonSituations":"Disk-full crash left a corrupt transcript; a backup/sync tool truncated the file; Claude Code format change made old parsers reject every record; someone hand-edited or copied the wrong file into the sessions directory.","solutions":["Inspect the file (head -c 500 path) to confirm whether it is valid JSONL at all","Restore the session file from backup or let Claude Code regenerate it","Delete the corrupt file and re-list sessions so the dead entry disappears","Update the OMP package — a newer parser may accept the changed Claude format"],"exampleFix":"// before\nawait store.load({ id, path: '/home/me/.claude/projects/x/session.jsonl', source: 'claude' });\n// after\nconst stat = await fs.stat(path);\nconst text = await Bun.file(path).text();\nif (!text.trim().startsWith('{')) throw new Error(`corrupt session file: ${path}`);\nawait store.load({ id, path, source: 'claude' });","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs\";\nexport function isPlausibleClaudeSessionFile(path: string): boolean {\n  try {\n    const stat = fs.statSync(path);\n    if (stat.size === 0) return false;\n    const head = fs.readFileSync(path, 'utf8').slice(0, 256).trimStart();\n    return head.startsWith('{'); // JSONL should start with a JSON object\n  } catch {\n    return false;\n  }\n}\n// call: if (!isPlausibleClaudeSessionFile(info.path)) skip;","typeGuard":"function isNoReadableRecordsError(err: unknown): boolean {\n  return err instanceof Error && /contains no readable records/.test(err.message);\n}","tryCatchPattern":null,"preventionTips":["Pre-validate that the session file starts with parseable JSON before importing","Restore from backup rather than forcing load on files that survived a disk-full crash","Drop dead session files from the store directory instead of retrying loads","Watch for Claude Code format upgrades and update the importer accordingly"],"tags":["session","parsing","corruption","claude"],"backgroundTag":"session-file-corrupt","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}