can1357/oh-my-pi · error · Error
Claude session contains no readable records
Error message
Claude session ${info.id} contains no readable records What it means
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.
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
Example fix
// before
await store.load({ id, path: '/home/me/.claude/projects/x/session.jsonl', source: 'claude' });
// after
const stat = await fs.stat(path);
const text = await Bun.file(path).text();
if (!text.trim().startsWith('{')) throw new Error(`corrupt session file: ${path}`);
await store.load({ id, path, source: 'claude' }); Defensive patterns
Strategy: validation
Validate before calling
import * as fs from "node:fs";
export function isPlausibleClaudeSessionFile(path: string): boolean {
try {
const stat = fs.statSync(path);
if (stat.size === 0) return false;
const head = fs.readFileSync(path, 'utf8').slice(0, 256).trimStart();
return head.startsWith('{'); // JSONL should start with a JSON object
} catch {
return false;
}
}
// call: if (!isPlausibleClaudeSessionFile(info.path)) skip; Type guard
function isNoReadableRecordsError(err: unknown): boolean {
return err instanceof Error && /contains no readable records/.test(err.message);
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot resume session
- Codex session at is empty or malformed
- Invalid tar octal value
- Unable to read Claude session
- Aborting parsing document;
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d792356d2eb6b722.
Report an issue: GitHub.
Appendix: source
Thrown at packages/coding-agent/src/session/claude-session-store.ts:359
}
return sessions.sort(
(left, right) => right.modified.getTime() - left.modified.getTime() || left.path.localeCompare(right.path),
);
}
/** Loads and converts a Claude transcript while preserving its source tree and timestamps. */
async load(info: ForeignSessionInfo): Promise<SessionManager> {
if (info.source !== this.source) throw new Error(`Cannot load ${info.source} session with ClaudeSessionStore`);
let records: ForeignJsonRecord[];
let stats: fsTypes.Stats;
try {
[records, stats] = await Promise.all([collectForeignJsonRecords(info.path), fs.stat(info.path)]);
} catch (error) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`Unable to read Claude session ${info.id}: ${detail}`);
}
if (records.length === 0 && stats.size > 0)
throw new Error(`Claude session ${info.id} contains no readable records`);
const sourceParents = new Map<string, string | null>();
let sourceCwd: string | undefined;
let sourceTitle: string | undefined;
let aiTitle: string | undefined;
for (const { value } of records) {
const uuid = stringField(value, "uuid");
if (uuid) sourceParents.set(uuid, stringField(value, "parentUuid") ?? null);
if (!sourceCwd) sourceCwd = stringField(value, "cwd");
if (value.type === "custom-title") sourceTitle = stringField(value, "customTitle") ?? sourceTitle;
if (value.type === "ai-title") aiTitle = stringField(value, "aiTitle") ?? aiTitle;
}
const manager = SessionManager.inMemory(sourceCwd ?? info.cwd);
const sourceTails = new Map<string, string>();
const usedIds = new Set<string>();
const toolNames = new Map<string, string>();
let lastModel: string | undefined;View on GitHub (pinned to 9690622007)