paperclipai/paperclip · error
codex_history_identity_mismatch
codex_history_identity_mismatch
Error message
codex_history_identity_mismatch
What it means
The thread/turns/list and thread/items/list handlers only serve history for the thread this transport instance is currently bound to (#threadId). Requesting history for any other threadId throws, since the retained runner event window belongs to a different thread.
Source
Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:3349
: undefined;
await this.#command(
"turn.steer",
{
text,
turnId: this.#durableTurnId,
providerTurnId: expectedTurnId,
...(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 });
}View on GitHub (pinned to 01ad858492)
Solutions
- Read the current threadId (e.g. via thread/read or transport state) and request history for that id.
- Create a new transport/session bound to the desired thread before listing its turns/items.
- Fix the client to use the threadId returned from thread/start rather than a cached value.
- Normalize thread ids before comparison if formatting differs.
Example fix
// before
await transport.request('thread/turns/list', { threadId: oldThreadId });
// after
const { thread } = await transport.request('thread/read', { threadId: transport.currentThreadId });
await transport.request('thread/turns/list', { threadId: transport.currentThreadId }); Defensive patterns
Strategy: validation
Validate before calling
const current = transport.currentThreadId ?? (await transport.request('thread/read', {})).thread?.id;
if (threadId !== current) throw new Error(`transport bound to ${current}, not ${threadId}`); Try / catch
try {
await transport.request('thread/items/list', { threadId, turnId });
} catch (err) {
if ((err as Error).message === 'codex_history_identity_mismatch') {
threadId = await resolveBoundThreadId(transport);
await transport.request('thread/items/list', { threadId, turnId });
} else throw err;
} Prevention
- Always use threadIds returned by thread/start/thread/read of the same transport
- Clear cached thread ids on reconnect
- Scope one transport instance per thread
When it happens
Trigger: Calling request('thread/turns/list' | 'thread/items/list', { threadId: X }) where X !== this.#threadId — e.g. a stale client handle, a thread id from a previous session, or a mismatched id format.
Common situations: Client cache holding an old threadId after reconnect; reading another company/thread's history through the same transport; id normalization differences (prefix vs full id); adopting a runner whose bound thread differs from the client's expectation.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- ACPX recovery identity does not match the persisted runtime
- Warm run transition target conflicts with its durable receip
- ACPX runtime omitted acpxRecordId
- ACPX runtime omitted backendSessionId
- ACPX provider runtime executable lease is invalid
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/23cc6d6d00beb7c8.
Report an issue: GitHub.