{"record":{"id":"1a119b5f768b209c","repo":"paperclipai/paperclip","slug":"acpx-provider-identity-is-incomplete","errorCode":null,"errorMessage":"ACPX provider identity is incomplete","messagePattern":"ACPX provider identity is incomplete","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/codex/codex-driver-values.ts","lineNumber":63,"sourceCode":"  const requiredStrings = [\n    \"normalizedSessionId\",\n    \"acpxRecordId\",\n    \"backendSessionId\",\n    \"agentSessionId\",\n    \"profileDigest\",\n    \"workspaceDigest\",\n    \"requestedModel\",\n    \"effectiveModel\",\n  ] as const;\n  if (\n    requiredStrings.some(\n      (key) =>\n        typeof identity[key] !== \"string\" ||\n        identity[key].length === 0 ||\n        identity[key].length > 240,\n    )\n  ) {\n    throw new Error(\"ACPX provider identity is incomplete\");\n  }\n  const permissionMode = identity.permissionMode;\n  if (\n    permissionMode !== undefined &&\n    permissionMode !== \"approve-all\" &&\n    permissionMode !== \"approve-reads\" &&\n    permissionMode !== \"deny-all\"\n  ) {\n    throw new Error(\n      \"ACPX provider identity contains an invalid permission mode\",\n    );\n  }\n  const fenceCandidates = identity.providerLifetimeFenceCandidates;\n  if (\n    !Array.isArray(fenceCandidates) ||\n    fenceCandidates.length !== 3 ||\n    fenceCandidates.some(\n      (candidate) =>","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/codex/codex-driver-values.ts#L45-L81","documentation":"parseProviderIdentity rehydrates a persisted ACPX provider identity from stored/serialized state. It requires eight string fields — normalizedSessionId, acpxRecordId, backendSessionId, agentSessionId, profileDigest, workspaceDigest, requestedModel, effectiveModel — each a non-empty string of at most 240 characters. If any is missing, not a string, empty, or longer than 240 characters, it throws \"ACPX provider identity is incomplete\" rather than returning a partially populated identity.","triggerScenarios":"Calling parseProviderIdentity on an object with kind \"acpx\" where any required key is absent, null, a non-string (e.g. number), \"\", or >240 chars — e.g. parseProviderIdentity({ kind: \"acpx\", normalizedSessionId: \"s\", acpxRecordId: \"r\" }) (all other keys missing) or a stored record where backendSessionId was serialized as undefined.","commonSituations":"Schema/version drift where an older persisted record predates a newly required field; JSON round-trip dropping keys with undefined values; manual edits or partial writes to the persisted provider record; corruption of the digest fields by a failed profile/workspace digest computation that stored \"\".","solutions":["Inspect the persisted provider identity object and populate every required key (normalizedSessionId, acpxRecordId, backendSessionId, agentSessionId, profileDigest, workspaceDigest, requestedModel, effectiveModel) with non-empty strings ≤240 chars.","If the record predates a schema change, re-create the session/identity via the normal launch path instead of loading the stale record.","Check the serialization path: ensure undefined/empty fields are not dropped silently by JSON.stringify or partial-update DB writes.","Validate before persisting (same field checks at write time) so incomplete records can never be stored."],"exampleFix":"// before\nconst identity = { kind: \"acpx\", normalizedSessionId: session.id, acpxRecordId: record.id };\nparseProviderIdentity(identity); // throws\n// after\nconst identity = {\n  kind: \"acpx\",\n  normalizedSessionId: session.id,\n  acpxRecordId: record.id,\n  backendSessionId: backend.id,\n  agentSessionId: agent.id,\n  profileDigest: profile.digest,\n  workspaceDigest: workspace.digest,\n  requestedModel: requested,\n  effectiveModel: effective,\n};\nparseProviderIdentity(identity); // ok","handlingStrategy":"type-guard","validationCode":"const REQUIRED_KEYS = [\"normalizedSessionId\",\"acpxRecordId\",\"backendSessionId\",\"agentSessionId\",\"profileDigest\",\"workspaceDigest\",\"requestedModel\",\"effectiveModel\"] as const;\nconst complete = REQUIRED_KEYS.every((k) =>\n  typeof identity[k] === \"string\" && identity[k].length > 0 && identity[k].length <= 240\n);\nif (!complete) throw new Error(\"refusing to persist/load incomplete ACPX identity\");","typeGuard":"function isCompleteApxIdentity(v: unknown): v is Record<string, string> {\n  if (typeof v !== \"object\" || v === null) return false;\n  const r = v as Record<string, unknown>;\n  if (r.kind !== \"acpx\") return false;\n  return ([\"normalizedSessionId\",\"acpxRecordId\",\"backendSessionId\",\"agentSessionId\",\"profileDigest\",\"workspaceDigest\",\"requestedModel\",\"effectiveModel\"] as const)\n    .every((k) => typeof r[k] === \"string\" && (r[k] as string).length > 0 && (r[k] as string).length <= 240);\n}","tryCatchPattern":"let identity: PersistedHarnessProviderIdentity | undefined;\ntry {\n  identity = parseProviderIdentity(stored);\n} catch (error) {\n  if (error instanceof Error && error.message === \"ACPX provider identity is incomplete\") {\n    identity = undefined; // fall back to relaunching the session to rebuild identity\n  } else {\n    throw error;\n  }\n}","preventionTips":["Validate identities at persist time with the same rules used at load time.","Avoid storing records with undefined fields through JSON round-trips; use explicit nulls or reject at write.","When adding required fields to the identity schema, write a migration/backfill for existing records.","On parse failure, prefer re-creating the identity via a fresh launch over hand-patching stored records."],"tags":["schema-validation","codex","acpx","persistence"],"backgroundTag":"schema-validation-failed","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"}