paperclipai/paperclip · error · HarnessReconciliationError
thread/items/list returned a different turn
Error message
thread/items/list returned a different turn
What it means
readCodexTurnItems pages thread/items/list for a specific turnId and verifies each row's turnId matches. When the provider returns items belonging to a different turn, the library throws a HarnessReconciliationError, since mixing items across turns would corrupt turn reconstruction.
Source
Thrown at packages/paperclip-runner/src/drivers/codex/codex-history.ts:112
);
}
return text(value.id);
},
);
}
export async function readCodexTurnItems(
transport: Requester,
threadId: string,
turnId: string,
): Promise<Record<string, unknown>[]> {
const entries = await pages(
transport,
"thread/items/list",
{ threadId, turnId, sortDirection: "asc" },
(value) => {
if (text(value.turnId) !== turnId)
throw new HarnessReconciliationError(
"thread/items/list returned a different turn",
);
return text(record(value.item).id);
},
);
return entries.map((entry) => record(entry.item));
}
View on GitHub (pinned to 01ad858492)
Solutions
- Verify the turnId comes from readCodexTurnMetadata for the same threadId in the same reconciliation pass.
- Restart the app-server and re-run reconciliation; repeated-cursor filtering bugs are usually provider-side, so upgrade Codex if reproducible.
- Retry after the turn reaches a terminal status; listing items mid-turn can race with turn transitions.
- Fail closed — items from the wrong turn must not be merged into reconstruction.
Defensive patterns
Strategy: try-catch
Validate before calling
if (!threadTurnIds.has(turnId)) throw new Error('turnId not present in this thread\'s turn metadata'); Type guard
function isForeignTurnItem(v: Record<string, unknown>, turnId: string): boolean { return String(v.turnId ?? '') !== turnId; } Try / catch
try { return await readCodexTurnItems(transport, threadId, turnId); } catch (e) { if (e instanceof HarnessReconciliationError && e.message.includes('different turn')) return retryAfterTerminal(transport, threadId, turnId); throw e; } Prevention
- Derive turnId from readCodexTurnMetadata in the same pass
- Wait for terminal turn status before listing items
- Upgrade app-server builds with item filtering bugs
- Never merge foreign-turn items into reconstruction
When it happens
Trigger: A thread/items/list page includes an entry whose value.turnId differs from the requested turnId — provider filtering bug, cursor bleed across turns, or caller passing the wrong turnId to a shared thread.
Common situations: Codex app-server returning unfiltered items when a turn has no items of its own; race where a new turn starts while its predecessor's items are still being listed; wrong turnId from stale recovery metadata.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- thread/read returned a different driver session
- codex_history_read_failed
- PRP recovery identity does not match the durable session bin
- device-login promotion: the account identifier cannot form a
- codex auth cache: account_id is not a valid account handle
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/7ee57d8f4df0c3e7.
Report an issue: GitHub.