{"record":{"id":"57c565d16f52cecc","repo":"paperclipai/paperclip","slug":"acpx-persisted-session-identity-mismatch","errorCode":"ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH","errorMessage":"The persisted ACPX session identity changed after admission","messagePattern":"The persisted ACPX session identity changed after admission","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts","lineNumber":1255,"sourceCode":"): Promise<AcpxModelStatus> {\n  const recordId = handle.acpxRecordId ?? handle.sessionKey;\n  const record = await sessionStore.load(recordId);\n  if (!record) {\n    throw Object.assign(\n      new Error(\"The pinned ACPX runtime omitted its persisted session record\"),\n      { code: \"ACPX_PERSISTED_SESSION_MISSING\" },\n    );\n  }\n  const persistedAgentSessionId =\n    nonEmptyRuntimeIdentity(record.agentSessionId) ?? record.acpSessionId;\n  if (\n    record.acpxRecordId !== identity.acpxRecordId ||\n    record.acpSessionId !== identity.backendSessionId ||\n    persistedAgentSessionId !== identity.agentSessionId\n  ) {\n    throw Object.assign(\n      new Error(\"The persisted ACPX session identity changed after admission\"),\n      { code: \"ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH\" },\n    );\n  }\n  const currentModelId = record.acpx?.current_model_id;\n  const availableModelIds = record.acpx?.available_models;\n  return {\n    summary: [\n      `session=${record.acpxRecordId}`,\n      `backendSessionId=${record.acpSessionId}`,\n      `agentSessionId=${persistedAgentSessionId}`,\n      record.closed === true ? \"closed\" : \"open\",\n    ].join(\" \"),\n    acpxRecordId: record.acpxRecordId,\n    backendSessionId: record.acpSessionId,\n    agentSessionId: persistedAgentSessionId,\n    lastRequestId: record.lastRequestId,\n    requestTokenUsage: structuredClone(record.request_token_usage ?? {}),\n    usageCost: structuredClone(record.cumulative_cost),\n    ...(currentModelId === undefined && !availableModelIds?.length","sourceCodeStart":1237,"sourceCodeEnd":1273,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts#L1237-L1273","documentation":"After loading the persisted ACPX session record, persistedRuntimeStatus() compares the record's stored identity (acpxRecordId, acpSessionId, agentSessionId) against the identity captured at admission time. If any field differs, the runtime mutated its session identity after the session was admitted, and the adapter throws this coded error (ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH) rather than reporting status based on a different underlying session. It protects against silently talking to a replaced backend session.","triggerScenarios":"Calling getStatus() where record.acpxRecordId !== identity.acpxRecordId, or record.acpSessionId !== identity.backendSessionId, or the record's agentSessionId/acpSessionId-derived agent id !== identity.agentSessionId.","commonSituations":"The ACPX runtime restarted and generated a new backend session id but reused the same record; a stale handle from a previous run is being checked against a rewritten record; session-store records from an older runtime version with different identity fields; a resume flow re-keying sessions without updating admission identity.","solutions":["Discard the stale handle and re-admit/re-create the ACPX session so identity and persisted record agree.","Investigate runtime logs for a restart or session re-key between admission and the status call; upgrade the runtime if it rewrites identity improperly.","If handles are persisted across process restarts, re-derive identity from the store instead of reusing the old admission identity.","Clear the mismatched record and respawn to restore a consistent (record, identity) pair."],"exampleFix":"// before\nconst status = await oldPort.getStatus(); // identity drifts from rewritten record\n// after\ntry {\n  const status = await oldPort.getStatus();\n} catch (e) {\n  if (e.code === \"ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH\") {\n    const fresh = await reacquireAcpxSession(handle.companyId); // new admission\n    return fresh.getStatus();\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"const record = await sessionStore.load(handle.acpxRecordId ?? handle.sessionKey);\nif (record && (record.acpxRecordId !== identity.acpxRecordId ||\n    record.acpSessionId !== identity.backendSessionId)) {\n  throw new Error(\"persisted identity drifted; re-admit session\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await port.getStatus();\n} catch (e) {\n  if (e.code === \"ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH\") {\n    return reacquireSession(handle); // fresh admission with matching identity\n  }\n  throw e;\n}","preventionTips":["Never reuse session handles across runtime restarts; re-derive identity after resume.","Treat any runtime re-key of acpSessionId as requiring full session re-admission.","Keep handle identity and store records in the same transaction/write path.","Monitor runtime logs for session-id rewrites between admission and use."],"tags":["identity-mismatch","session-state","acpx","integrity-check"],"backgroundTag":"invalid-state-transition","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"}