paperclipai/paperclip · error
ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH
ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH
Error message
The persisted ACPX session identity changed after admission
What it means
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.
Source
Thrown at packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts:1255
): Promise<AcpxModelStatus> {
const recordId = handle.acpxRecordId ?? handle.sessionKey;
const record = await sessionStore.load(recordId);
if (!record) {
throw Object.assign(
new Error("The pinned ACPX runtime omitted its persisted session record"),
{ code: "ACPX_PERSISTED_SESSION_MISSING" },
);
}
const persistedAgentSessionId =
nonEmptyRuntimeIdentity(record.agentSessionId) ?? record.acpSessionId;
if (
record.acpxRecordId !== identity.acpxRecordId ||
record.acpSessionId !== identity.backendSessionId ||
persistedAgentSessionId !== identity.agentSessionId
) {
throw Object.assign(
new Error("The persisted ACPX session identity changed after admission"),
{ code: "ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH" },
);
}
const currentModelId = record.acpx?.current_model_id;
const availableModelIds = record.acpx?.available_models;
return {
summary: [
`session=${record.acpxRecordId}`,
`backendSessionId=${record.acpSessionId}`,
`agentSessionId=${persistedAgentSessionId}`,
record.closed === true ? "closed" : "open",
].join(" "),
acpxRecordId: record.acpxRecordId,
backendSessionId: record.acpSessionId,
agentSessionId: persistedAgentSessionId,
lastRequestId: record.lastRequestId,
requestTokenUsage: structuredClone(record.request_token_usage ?? {}),
usageCost: structuredClone(record.cumulative_cost),
...(currentModelId === undefined && !availableModelIds?.lengthView on GitHub (pinned to 01ad858492)
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.
Example fix
// before
const status = await oldPort.getStatus(); // identity drifts from rewritten record
// after
try {
const status = await oldPort.getStatus();
} catch (e) {
if (e.code === "ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH") {
const fresh = await reacquireAcpxSession(handle.companyId); // new admission
return fresh.getStatus();
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const record = await sessionStore.load(handle.acpxRecordId ?? handle.sessionKey);
if (record && (record.acpxRecordId !== identity.acpxRecordId ||
record.acpSessionId !== identity.backendSessionId)) {
throw new Error("persisted identity drifted; re-admit session");
} Try / catch
try {
return await port.getStatus();
} catch (e) {
if (e.code === "ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH") {
return reacquireSession(handle); // fresh admission with matching identity
}
throw e;
} Prevention
- 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.
When it happens
Trigger: Calling getStatus() where record.acpxRecordId !== identity.acpxRecordId, or record.acpSessionId !== identity.backendSessionId, or the record's agentSessionId/acpSessionId-derived agent id !== identity.agentSessionId.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Anthropic pinned Agent version identity does not match the s
- Native ACPX backend for pi is unavailable until descriptor-c
- The Pi ACPX profile is not available
- native_session_attach_binding_mismatch
- initialize the ACPX sidecar first
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/57c565d16f52cecc.
Report an issue: GitHub.