Yeachan-Heo/oh-my-codex · error
Question record not found: ${recordPath}
Error message
Question record not found: ${recordPath} What it means
updateQuestionRecord reads the question record file before applying an updater; if readQuestionRecord returns null (file missing, unreadable, or unparseable into a record) it throws this error. The API refuses to 'update' a record that does not exist, mirroring a read-modify-write on a missing file.
Source
Thrown at src/question/state.ts:129
const recordPath = getQuestionRecordPath(cwd, questionId, sessionId);
await writeQuestionRecord(recordPath, record);
if (options.emitEvent) {
await appendQuestionEvent(cwd, 'question-created', record, {
recordPath,
timeoutMs: options.timeoutMs,
runId: options.runId,
now,
});
}
return { recordPath, record };
}
export async function updateQuestionRecord(
recordPath: string,
updater: (record: QuestionRecord) => QuestionRecord,
): Promise<QuestionRecord> {
const current = await readQuestionRecord(recordPath);
if (!current) throw new Error(`Question record not found: ${recordPath}`);
const updated = updater(current);
await writeQuestionRecord(recordPath, updated);
return updated;
}
export async function markQuestionPrompting(
recordPath: string,
renderer: QuestionRendererState,
options: { closeQuestionRenderer?: CloseQuestionRenderer } = {},
): Promise<QuestionRecord> {
return await withQuestionSubmitLock(recordPath, async () => {
let rendererToClose: QuestionRendererState | undefined;
const updated = await updateQuestionRecord(recordPath, (record) => {
if (isTerminalQuestionStatus(record.status)) {
rendererToClose = renderer;
return record;
}
return {View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Verify the record file exists and parses before calling update (readQuestionRecord !== null)
- Re-check lifecycle: ensure the record is created before any update call and not deleted by a concurrent cleanup job
- If racing with cleanup, serialize via the provided submit lock so cleanup cannot interleave
- Handle the error by recreating the record or surfacing 'question not found' to the user instead of retrying blindly
Example fix
// before
await updateQuestionRecord(path, (r) => ({ ...r, status: 'answered' }));
// after
if (!(await readQuestionRecord(path))) {
throw new Error(`question ${path} no longer exists; it may have expired`);
}
await updateQuestionRecord(path, (r) => ({ ...r, status: 'answered' })); Defensive patterns
Strategy: validation
Validate before calling
const current = await readQuestionRecord(recordPath);
if (!current) throw new Error('record missing — recreate or report expired question'); Try / catch
try { await updateQuestionRecord(p, fn); } catch (e) { if (/Question record not found/.test((e as Error).message)) { await handleExpiredQuestion(p); return; } throw e; } Prevention
- Create the record before scheduling any update
- Guard cleanup jobs so they don't delete records with pending operations
- Pass the exact recordPath returned by the create API
When it happens
Trigger: Calling updateQuestionRecord (or higher-level updated/markQuestionTerminalError) with a recordPath that was deleted, never created, or contains invalid JSON; concurrent deletion by another process between creation and update.
Common situations: Record file cleaned up by a sweeper/expiry while an answer was in flight; wrong path passed (typo, wrong session dir); crash after partial write leaving corrupt JSON; tests that mock the filesystem incompletely.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/1797ff9088ed923b.
Report an issue: GitHub.