paperclipai/paperclip · error · Error
ACPX recovery identity does not match the persisted runtime
Error message
ACPX recovery identity does not match the persisted runtime record
What it means
verifyExpectedAcpxIdentity compares an expected session identity against the persisted runtime identity record (profile/workspace digests, permission mode, and provider lifetime fence candidates). Any mismatch means the runtime state on disk belongs to a different identity than the one being recovered, so it throws to prevent resuming a mismatched ACPX session. This protects against attaching a recovered session to the wrong agent configuration.
Source
Thrown at packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts:190
if (persisted === null) return;
const record = parsePersistedRecord(persisted);
if (
record.acpxRecordId !== expected.acpxRecordId ||
record.backendSessionId !== expected.backendSessionId ||
record.agentSessionId !== expected.agentSessionId ||
record.normalizedSessionId !== binding.normalizedSessionId ||
record.profileDigest !== binding.profileDigest ||
record.workspaceDigest !== binding.workspaceDigest ||
record.requestedModel !== binding.requestedModel ||
record.effectiveModel !== binding.effectiveModel ||
record.permissionMode !== binding.permissionMode ||
!sameFenceCandidates(
record.providerLifetimeFenceCandidates,
expected.providerLifetimeFenceCandidates,
)
) {
throw new Error(
"ACPX recovery identity does not match the persisted runtime record",
);
}
}
function parsePersistedRecord(value: unknown): AcpxIdentityRecord {
const record = object(value);
rejectUnknownKeys(record, [
"schema",
"normalizedSessionId",
"acpxRecordId",
"backendSessionId",
"agentSessionId",
"profileDigest",
"workspaceDigest",
"requestedModel",
"effectiveModel",
"permissionMode",View on GitHub (pinned to 01ad858492)
Solutions
- Start a fresh ACPX session instead of recovering — the persisted record belongs to a previous identity.
- Re-run with the exact same agent profile, workspace contents, and permissionMode used when the record was persisted.
- Delete or archive the stale runtime directory for that session id so a new identity record can be written.
- If this happens after a package upgrade, expect old sessions to be unrecoverable; do not force-match the record.
Example fix
// before
await acpx.open({ sessionId: oldId, permissionMode: "bypassAll" }); // record had "default"
// after
await acpx.open({ sessionId: oldId, permissionMode: "default" }); // match persisted record
// or: start a new session entirely Defensive patterns
Strategy: try-catch
Try / catch
try {
await acpx.open({ sessionId, ...expected });
} catch (e) {
if (e.message === "ACPX recovery identity does not match the persisted runtime record") {
// fall back to a brand-new session; the old record is for a different identity
await acpx.open({ sessionId: newSessionId(), ...currentConfig });
} else throw e;
} Prevention
- Keep the same permissionMode and workspace contents across attempts to recover a session.
- Treat runner upgrades as invalidating persisted sessions.
- Clean up stale runtime directories when identity inputs change.
When it happens
Trigger: Calling open() / acpxProviderSessionIdentity() / createAcpxIdentityRecord() to recover an existing ACPX session where any of: record agent digest differs from binding digest, record.profileDigest differs, record.workspaceDigest differs, record.permissionMode !== binding.permissionMode, or the persisted fence candidates differ from expected.providerLifetimeFenceCandidates.
Common situations: Upgrading or downgrading the runner (profile digest changes) and then trying to resume an old session; editing the workspace between runs; launching with a different --permission-mode flag against a persisted session; reusing a runtime directory across sessions whose fence candidates were regenerated.
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
- persisted Codex ACPX resultless recovery requires a complete
- ACPX runtime omitted acpxRecordId
- ACPX runtime omitted backendSessionId
- codex_history_identity_mismatch
- [paperclip] sandbox callback bridge kept queued request ${re
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/3d6c062acfa20856.
Report an issue: GitHub.