can1357/oh-my-pi · error · Error
Unable to read Claude session ${info.id}: ${detail}
Error message
Unable to read Claude session ${info.id}: ${detail} What it means
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.
Source
Thrown at packages/coding-agent/src/session/claude-session-store.ts:356
} catch {
// Files may disappear while Claude rotates its session store.
}
}
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>();View on GitHub (pinned to 9690622007)
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
Example fix
// before
const session = await store.load(staleInfo); // staleInfo from an old listing
// after
const sessions = await store.list();
const info = sessions.find(s => s.id === wantedId);
if (!info) throw new Error('session no longer available');
const session = await store.load(info); Defensive patterns
Strategy: try-catch
Validate before calling
import * as fs from "node:fs";
// before load:
// const stat = fs.statSync(info.path); // throws ENOENT/EACCES early
// if (stat.size === 0) throw new Error(`empty session file: ${info.path}`); Type guard
function isMissingFileError(err: unknown): boolean {
return err instanceof Error && (err as NodeJS.ErrnoException).code === 'ENOENT';
} Try / catch
try {
const session = await store.load(info);
} catch (err) {
if (err instanceof Error && err.message.startsWith('Unable to read Claude session')) {
logger.warn('claude session unreadable, skipping', { id: info.id, cause: err.message });
return null; // degrade gracefully in session lists
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unable to read Codex session ${info.id} at ${info.path}
- Short write
- Session append failed and its partial bytes could not be rol
- Too many levels of symbolic links
- {}: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/70095a2cbbcec4fc.
Report an issue: GitHub.