paperclipai/paperclip · error
codex_history_unavailable
codex_history_unavailable
Error message
codex_history_unavailable: requested turn is outside the retained runner event window
What it means
For thread/items/list, the transport reconstructs items from the runner's retained committed-event window. If the requested turnId was never observed as started within that window (params.turnId !== this.#turnId), the history cannot be reconstructed and the transport throws instead of returning partial/truncated data.
Source
Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:3356
...(correlationId ? { correlationId } : {}),
},
correlationId,
);
return {};
}
if (method === "turn/interrupt") {
await this.#command("turn.interrupt", params);
return {};
}
if (method === "thread/turns/list" || method === "thread/items/list") {
if (params.threadId !== this.#threadId) throw new Error("codex_history_identity_mismatch");
const snapshot = await this.request("thread/read", { threadId: this.#threadId, includeTurns: false });
const turns = record(snapshot.thread).turns as Array<Record<string, unknown>>;
let data: Array<Record<string, unknown>>;
if (method === "thread/turns/list") {
data = turns.map(turn => ({ ...turn, items: [], itemsView: "notLoaded" }));
} else {
if (params.turnId !== this.#turnId) throw new Error("codex_history_unavailable: requested turn is outside the retained runner event window");
const items = new Map<string, Record<string, unknown>>();
let observedTurn = "";
let observedStart = false;
for (const event of this.#core?.store.state.committedEvents ?? []) {
const payload = record(record(event.envelope.payload).payload);
if (event.eventType === "turn.started") observedTurn = String(payload.providerTurnId ?? payload.turnId ?? record(payload.turn).id ?? "");
if (event.eventType === "turn.started" && observedTurn === params.turnId) observedStart = true;
if (event.eventType !== "item.completed" || observedTurn !== params.turnId) continue;
const item = record(rehydrateRunnerdItemNotification(payload, this.#threadId, observedTurn).item);
if (typeof item.id === "string") items.set(item.id, { turnId: observedTurn, item });
}
if (!observedStart) throw new Error("codex_history_incomplete: requested turn start is outside the retained runner event window");
data = [...items.values()];
}
if (params.sortDirection === "desc") data.reverse();
const offset = params.cursor == null ? 0 : Number(params.cursor);
if (!Number.isSafeInteger(offset) || offset < 0 || offset > data.length) throw new Error("codex_history_invalid_cursor");
const limit = typeof params.limit === "number" ? Math.max(1, Math.min(100, params.limit)) : 100;View on GitHub (pinned to 01ad858492)
Solutions
- Request the current turn id (via thread/read with includeTurns) and list items for that turn.
- Use the durable persistence layer (state directory / database) for turns outside the retained event window.
- Start a new turn before listing items; items only exist for turns the live runner has observed.
- Do not retry with the same turnId — the window cannot grow backwards.
Example fix
// before
await transport.request('thread/items/list', { threadId, turnId: staleTurnId });
// after
const snapshot = await transport.request('thread/read', { threadId, includeTurns: true });
const currentTurnId = snapshot.thread.turns.at(-1).id;
await transport.request('thread/items/list', { threadId, turnId: currentTurnId }); Defensive patterns
Strategy: fallback
Validate before calling
const turns = (await transport.request('thread/read', { threadId, includeTurns: true })).thread.turns;
if (!turns.some(t => t.id === turnId)) throw new Error('turn not in retained window; use durable store'); Try / catch
try {
items = await transport.request('thread/items/list', { threadId, turnId });
} catch (err) {
if ((err as Error).message.startsWith('codex_history_unavailable')) {
items = await loadItemsFromDurableStore(threadId, turnId);
} else throw err;
} Prevention
- Only request items for the current turn of a live transport
- Persist turns durably if history beyond the live window is needed
- Inform users when history is truncated by retention
When it happens
Trigger: Calling request('thread/items/list', { threadId: <current>, turnId: X }) where X is not the transport's current #turnId — a turn from before the retained window or a future/unknown turn.
Common situations: Paging back through history past the retention window; requesting items for a turn that ran under a previous runner process; a client resuming after restart with an outdated turnId.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- codex_history_read_failed
- codex_history_incomplete
- codex_history_identity_mismatch
- codex_history_incomplete
- codex_history_invalid_cursor
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/d122d907ededf545.
Report an issue: GitHub.