{"record":{"id":"5556a992125d58dd","repo":"paperclipai/paperclip","slug":"codex-history-incomplete-5556a9","errorCode":"codex_history_incomplete","errorMessage":"codex_history_incomplete: requested turn start is outside the retained runner event window","messagePattern":"codex_history_incomplete: requested turn start is outside the retained runner event window","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/live/runnerd-codex-transport.ts","lineNumber":3368,"sourceCode":"      const snapshot = await this.request(\"thread/read\", { threadId: this.#threadId, includeTurns: false });\n      const turns = record(snapshot.thread).turns as Array<Record<string, unknown>>;\n      let data: Array<Record<string, unknown>>;\n      if (method === \"thread/turns/list\") {\n        data = turns.map(turn => ({ ...turn, items: [], itemsView: \"notLoaded\" }));\n      } else {\n        if (params.turnId !== this.#turnId) throw new Error(\"codex_history_unavailable: requested turn is outside the retained runner event window\");\n        const items = new Map<string, Record<string, unknown>>();\n        let observedTurn = \"\";\n        let observedStart = false;\n        for (const event of this.#core?.store.state.committedEvents ?? []) {\n          const payload = record(record(event.envelope.payload).payload);\n          if (event.eventType === \"turn.started\") observedTurn = String(payload.providerTurnId ?? payload.turnId ?? record(payload.turn).id ?? \"\");\n          if (event.eventType === \"turn.started\" && observedTurn === params.turnId) observedStart = true;\n          if (event.eventType !== \"item.completed\" || observedTurn !== params.turnId) continue;\n          const item = record(rehydrateRunnerdItemNotification(payload, this.#threadId, observedTurn).item);\n          if (typeof item.id === \"string\") items.set(item.id, { turnId: observedTurn, item });\n        }\n        if (!observedStart) throw new Error(\"codex_history_incomplete: requested turn start is outside the retained runner event window\");\n        data = [...items.values()];\n      }\n      if (params.sortDirection === \"desc\") data.reverse();\n      const offset = params.cursor == null ? 0 : Number(params.cursor);\n      if (!Number.isSafeInteger(offset) || offset < 0 || offset > data.length) throw new Error(\"codex_history_invalid_cursor\");\n      const limit = typeof params.limit === \"number\" ? Math.max(1, Math.min(100, params.limit)) : 100;\n      return { data: data.slice(offset, offset + limit), nextCursor: offset + limit < data.length ? String(offset + limit) : null };\n    }\n    if (method === \"thread/read\") {\n      if (this.#core === null) {\n        this.#recoveryTurnBindingPending = true;\n        await this.#resume();\n      }\n      // Ask the authenticated runner for its live provider snapshot rather\n      // than reading its filesystem. This both supports remote process owners\n      // and proves any identity restored after PRP event compaction before the\n      // checkpoint-backed thread is exposed to the driver.\n      const snapshot = await this.#commandResult(\"session.snapshot\", {});","sourceCodeStart":3350,"sourceCodeEnd":3386,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/live/runnerd-codex-transport.ts#L3350-L3386","documentation":"When reconstructing thread/items/list, the code walks committed events and requires an explicit turn.started event for the requested turn; item.completed events alone are insufficient. If the retained event window begins mid-turn (start event evicted), the transport throws rather than emit an item list of unknown completeness.","triggerScenarios":"Requesting thread/items/list for the current turn whose turn.started event has already aged out of this.#core.store.state.committedEvents, or where the turn's providerTurnId/turnId in the start event doesn't match params.turnId.","commonSituations":"Very long turns producing many events that evict the start marker; attaching/adopting a runner mid-turn so the start event predates the retained window; replaying history after event-window trimming.","solutions":["List items for turns whose start event is still retained; use thread/read to discover which turns qualify.","Increase the runner's event retention/window size if long turns must remain fully replayable.","Recover the turn's items from durable storage (state directory) instead of the live event window.","Re-run the turn if the items must be sourced live and the window cannot be extended."],"exampleFix":"// before\nawait transport.request('thread/items/list', { threadId, turnId: evictedTurnId });\n// after\nconst turns = (await transport.request('thread/read', { threadId, includeTurns: true })).thread.turns;\nconst latest = turns.at(-1).id;\nawait transport.request('thread/items/list', { threadId, turnId: latest });","handlingStrategy":"fallback","validationCode":"// ensure the turn start is still observable before listing items\nconst read = await transport.request('thread/read', { threadId, includeTurns: true });\nif (!read.thread.turns.some(t => t.id === turnId)) fallbackToDurableStore();","typeGuard":null,"tryCatchPattern":"try {\n  data = await transport.request('thread/items/list', { threadId, turnId });\n} catch (err) {\n  if ((err as Error).message.startsWith('codex_history_incomplete')) {\n    data = await loadItemsFromDurableStore(threadId, turnId);\n  } else throw err;\n}","preventionTips":["Increase event-window retention for long-running turns","Capture items incrementally as the turn progresses","Adopt runners before their turns start so start events are retained"],"tags":["history","events","incomplete-data"],"backgroundTag":"empty-result-set","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"}