{"record":{"id":"da7826298897b0bb","repo":"paperclipai/paperclip","slug":"codex-thread-response-omitted-thread-id","errorCode":null,"errorMessage":"Codex thread response omitted thread.id","messagePattern":"Codex thread response omitted thread\\.id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/codex/codex-app-server-driver-impl.ts","lineNumber":788,"sourceCode":"      // supports, so the capability stays as advertised and the goal is merely\n      // unknown until the next call.\n      this.#options.onDiagnostic?.(\n        redactCodexDiagnostic(`thread goal probe failed: ${String(error)}`),\n      );\n      return undefined;\n    }\n  }\n\n  #openedThread(\n    response: Record<string, unknown>,\n    initialize: Record<string, unknown>,\n    workingDirectory: string,\n    collaborationMode: Record<string, unknown> | null,\n  ): OpenedCodexThread {\n    const thread = record(response.thread);\n    const threadId = text(thread.id);\n    if (threadId.length === 0)\n      throw new Error(\"Codex thread response omitted thread.id\");\n    const providerSessionId = text(thread.sessionId) || null;\n    if (\n      this.#options.requireProviderSessionIdentity &&\n      providerSessionId === null\n    ) {\n      throw new Error(\n        `provider_initialize_protocol_error: provider=${this.#options.driverIdentity?.kind ?? \"codex\"} stage=session.open omitted provider session identity`,\n      );\n    }\n    const activePermissionProfile = record(thread.activePermissionProfile);\n    const permissionProfileId = text(activePermissionProfile.id);\n    const requestedMode = this.#options.requestedCollaborationMode ?? \"default\";\n    const requiredPermissionProfile =\n      text(createSecuredCodexThreadParams(workingDirectory, requestedMode, true, false, this.#options.environment).permissions);\n    if (\n      permissionProfileId.length > 0 &&\n      permissionProfileId !== requiredPermissionProfile\n    ) {","sourceCodeStart":770,"sourceCodeEnd":806,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/codex/codex-app-server-driver-impl.ts#L770-L806","documentation":"The Codex app-server driver throws this when the `thread/new` (session open) response does not contain a usable `thread.id` string. Paperclip requires a stable thread identifier to correlate all subsequent turns with the provider session, so an empty or missing id is treated as a protocol violation by the provider. The driver fails fast in `#openedThread` rather than returning a thread that cannot be addressed.","triggerScenarios":"Calling `opened()` after starting a Codex app-server session when the provider's response `thread` object lacks an `id` field, or `text(thread.id)` returns an empty string (e.g. provider returned `{}` or `id: \"\"`).","commonSituations":"Running against an older or nonstandard Codex app-server build whose thread/new payload schema differs; a version mismatch between the adapter protocol expectations and the installed Codex binary; mocking/stub drivers that omit the field.","solutions":["Check the installed Codex app-server version and upgrade to one that returns thread.id in the thread/new response","Log the raw response.thread payload before this point to confirm the provider actually omitted id","If using a custom/proxy app-server, make it echo back a non-empty thread.id","Update the adapter protocol expectations if Codex renamed the field"],"exampleFix":"// before (provider response)\n{ \"thread\": { \"sessionId\": \"s-1\" } }\n// after (correct provider response)\n{ \"thread\": { \"id\": \"thr_abc123\", \"sessionId\": \"s-1\" } }","handlingStrategy":"try-catch","validationCode":"const thread = record(response.thread);\nif (!thread || typeof thread.id !== \"string\" || thread.id.length === 0) {\n  throw new Error(\"provider response missing thread.id\");\n}","typeGuard":"function hasThreadId(t: unknown): t is { id: string } {\n  return typeof t === \"object\" && t !== null && typeof (t as { id?: unknown }).id === \"string\" && (t as { id: string }).id.length > 0;\n}","tryCatchPattern":"try {\n  const opened = await driver.opened(...);\n} catch (err) {\n  if (err.message.includes(\"omitted thread.id\")) {\n    logProviderProtocolViolation(response); // capture raw payload, retry with healthy provider\n  }\n  throw err;\n}","preventionTips":["Pin and health-check the Codex app-server version before admitting sessions","Contract-test provider responses against the expected thread/new schema","Log raw provider payloads at session-open for diagnosability"],"tags":["codex","protocol","session"],"backgroundTag":"missing-required-argument","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}