{"record":{"id":"f3202a57c468cbdf","repo":"paperclipai/paperclip","slug":"acpx-runtime-omitted-acpxrecordid","errorCode":null,"errorMessage":"ACPX runtime omitted acpxRecordId","messagePattern":"ACPX runtime omitted acpxRecordId","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts","lineNumber":1811,"sourceCode":"      status,\n      tokenBudget:\n        goal.tokenBudget === null ? null : optionalNumber(goal.tokenBudget),\n      tokensUsed: optionalNumber(goal.tokensUsed),\n      timeUsedSeconds: optionalNumber(goal.timeUsedSeconds),\n      iterations: optionalNumber(goal.iterations),\n      lastReason:\n        goal.lastReason === null || typeof goal.lastReason === \"string\"\n          ? goal.lastReason\n          : undefined,\n      createdAt: optionalTimestamp(goal.createdAt),\n      updatedAt: optionalTimestamp(goal.updatedAt),\n    },\n  };\n}\n\nfunction requireIdentity(handle: AcpRuntimeHandle): AcpxRuntimePortIdentity {\n  const acpxRecordId = nonEmptyRuntimeIdentity(handle.acpxRecordId);\n  if (!acpxRecordId) throw new Error(\"ACPX runtime omitted acpxRecordId\");\n  const backendSessionId = nonEmptyRuntimeIdentity(handle.backendSessionId);\n  if (!backendSessionId) {\n    throw new Error(\"ACPX runtime omitted backendSessionId\");\n  }\n  return {\n    acpxRecordId,\n    backendSessionId,\n    // ACPX agents do not all advertise a distinct native thread identity.\n    // In that case the backend ID is the real ACP protocol session, so retain\n    // it explicitly rather than inventing a Paperclip-owned identifier.\n    agentSessionId:\n      nonEmptyRuntimeIdentity(handle.agentSessionId) ?? backendSessionId,\n  };\n}\n\nfunction definedEnvironment(\n  environment: Readonly<NodeJS.ProcessEnv>,\n): Record<string, string> {","sourceCodeStart":1793,"sourceCodeEnd":1829,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts#L1793-L1829","documentation":"requireIdentity() validates that an ACP runtime handle carries a non-empty acpxRecordId before the adapter will open runtime ports. The acpxRecordId is the persistent identifier of the ACPX runtime record in the control plane; without it the adapter cannot bind or verify which runtime record a session belongs to, so it throws instead of proceeding with a corrupt/anonymous handle.","triggerScenarios":"Passing an AcpRuntimeHandle whose acpxRecordId is undefined, null, empty string, or whitespace-only into requireIdentity() — typically when a handle was constructed from an incomplete spawn/bootstrap result, or a deserialized handle lost the field.","commonSituations":"A runtime record failed to persist before the handle was created (DB write failure upstream); an older ACPX runtime version that did not return acpxRecordId; code that manually constructs a handle object and forgets the field; JSON round-trip dropping undefined fields.","solutions":["Ensure the runtime bootstrap path persists the ACPX runtime record and copies its id into handle.acpxRecordId before opening ports.","Validate the handle shape right after obtaining it (assert typeof handle.acpxRecordId === 'string' && handle.acpxRecordId.trim() !== '').","Upgrade/check the ACPX runtime version so it reports acpxRecordId in its bootstrap response.","If the handle came from persistence, confirm the record row exists and was not deleted mid-session."],"exampleFix":"// before: handle built without the record id\nconst handle = { backendSessionId: session.id } as AcpRuntimeHandle;\nconst identity = requireIdentity(handle);\n\n// after\nconst handle = { acpxRecordId: runtimeRecord.id, backendSessionId: session.id } as AcpRuntimeHandle;\nconst identity = requireIdentity(handle);","handlingStrategy":"validation","validationCode":"function hasAcpxRecordId(h) {\n  return typeof h?.acpxRecordId === 'string' && h.acpxRecordId.trim().length > 0;\n}\nif (!hasAcpxRecordId(handle)) throw new Error('handle missing acpxRecordId before port setup');","typeGuard":"function hasAcpxRecordId(h: AcpRuntimeHandle): h is AcpRuntimeHandle & { acpxRecordId: string } {\n  return typeof h.acpxRecordId === 'string' && h.acpxRecordId.trim() !== '';\n}","tryCatchPattern":"try {\n  const identity = requireIdentity(handle);\n  openPorts(identity);\n} catch (err) {\n  if (err.message === 'ACPX runtime omitted acpxRecordId') {\n    // re-bootstrap the runtime record and rebuild the handle\n    handle = await rebuildRuntimeHandle();\n  } else throw err;\n}","preventionTips":["Build handles only through the bootstrap factory that persists the runtime record and copies its id.","Validate handle shape immediately after deserialization or JSON round-trips.","Pin ACPX runtime versions so the record id is always present in bootstrap output."],"tags":["identity","validation","acpx","handle"],"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"}