can1357/oh-my-pi · error · Error

Codex session ${info.id} at ${info.path} is empty or malform

Error message

Codex session ${info.id} at ${info.path} is empty or malformed

What it means

CodexSessionStore.load() successfully read the rollout file but readJsonLines yielded zero records, meaning the file is empty or every line failed parsing. Thrown to distinguish 'readable but no usable content' from a hard read failure.

Source

Thrown at packages/coding-agent/src/session/codex-session-store.ts:551

				modified,
			});
		}
		sessions.sort(
			(left, right) => right.modified.getTime() - left.modified.getTime() || left.id.localeCompare(right.id),
		);
		return sessions;
	}

	/** Converts one Codex rollout into a non-persistent OMP session. */
	async load(info: ForeignSessionInfo): Promise<SessionManager> {
		if (info.source !== "codex") throw new Error(`Cannot load ${info.source} session with CodexSessionStore`);
		let records: Record<string, unknown>[];
		try {
			records = await readJsonLines(info.path);
		} catch (error) {
			throw new Error(`Unable to read Codex session ${info.id} at ${info.path}`, { cause: error });
		}
		if (records.length === 0) throw new Error(`Codex session ${info.id} at ${info.path} is empty or malformed`);

		const metadata = records.find(record => record.type === "session_meta" && isRecord(record.payload));
		const cwd =
			metadata && isRecord(metadata.payload) ? (stringField(metadata.payload, "cwd") ?? info.cwd) : info.cwd;
		const manager = SessionManager.inMemory(cwd);
		const canonical = canonicalTexts(records);
		const converted: ConvertedRecord[] = [];
		const toolNames = new Map<string, string>();
		let model = "codex";
		let fallbackTimestamp = info.created.getTime();
		let title = info.title;

		for (const record of records) {
			const timestamp = timestampMillis(record.timestamp, fallbackTimestamp);
			fallbackTimestamp = Math.max(fallbackTimestamp + 1, timestamp);
			if (!isRecord(record.payload)) continue;
			let item: ConvertedRecord | undefined;
			if (record.type === "turn_context") {

View on GitHub (pinned to 9690622007)

Solutions

  1. Check file size (ls -l info.path) — a 0-byte file means Codex never wrote the session
  2. Re-run the Codex session to regenerate a real rollout, then re-list and load
  3. Remove the empty/stub file so stale listings stop offering it
  4. Point info.path at the actual rollout file if the wrong path was passed

Example fix

// before
const manager = await codexStore.load(info);
// after
const stat = await fs.stat(info.path);
if (stat.size === 0) throw new Error(`Codex rollout is empty: ${info.path}`);
const manager = await codexStore.load(info);
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs";
export function isNonEmptyCodexRollout(path: string): boolean {
  try {
    const content = fs.readFileSync(path, 'utf8');
    return content.split('\n').some(line => line.trim().length > 0);
  } catch {
    return false;
  }
}

Type guard

function isEmptyOrMalformedCodexSession(err: unknown): boolean {
  return err instanceof Error && /is empty or malformed$/.test(err.message);
}

Prevention

When it happens

Trigger: Calling load(info) on a zero-byte rollout file, a file containing only blank lines, or a file whose lines are all malformed JSON so readJsonLines returns an empty array.

Common situations: Codex created the session file but crashed before writing the session_meta line; disk-full left a stub file; a sync tool synced an empty placeholder; the wrong file was passed as info.path.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ec3f7ef49c2f0ef9. Report an issue: GitHub.