{"record":{"id":"7ee57d8f4df0c3e7","repo":"paperclipai/paperclip","slug":"thread-items-list-returned-a-different-turn","errorCode":null,"errorMessage":"thread/items/list returned a different turn","messagePattern":"thread/items/list returned a different turn","errorType":"error_code","errorClass":"HarnessReconciliationError","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/codex/codex-history.ts","lineNumber":112,"sourceCode":"        );\n      }\n      return text(value.id);\n    },\n  );\n}\n\nexport async function readCodexTurnItems(\n  transport: Requester,\n  threadId: string,\n  turnId: string,\n): Promise<Record<string, unknown>[]> {\n  const entries = await pages(\n    transport,\n    \"thread/items/list\",\n    { threadId, turnId, sortDirection: \"asc\" },\n    (value) => {\n      if (text(value.turnId) !== turnId)\n        throw new HarnessReconciliationError(\n          \"thread/items/list returned a different turn\",\n        );\n      return text(record(value.item).id);\n    },\n  );\n  return entries.map((entry) => record(entry.item));\n}\n","sourceCodeStart":94,"sourceCodeEnd":120,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/codex/codex-history.ts#L94-L120","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!threadTurnIds.has(turnId)) throw new Error('turnId not present in this thread\\'s turn metadata');","typeGuard":"function isForeignTurnItem(v: Record<string, unknown>, turnId: string): boolean { return String(v.turnId ?? '') !== turnId; }","tryCatchPattern":"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; }","preventionTips":["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"],"tags":["codex","turn","identity","reconciliation"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}