{"record":{"id":"9aa329412de90a62","repo":"paperclipai/paperclip","slug":"acpx-agent-runtime-executable-digest-mismatch","errorCode":null,"errorMessage":"ACPX ${agent} runtime executable digest mismatch","messagePattern":"ACPX (.+?) runtime executable digest mismatch","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts","lineNumber":1094,"sourceCode":"    );\n    const beforeIdentity = fileIdentity(before);\n    const afterIdentity = fileIdentity(after);\n    if (\n      position !== Number(before.size) ||\n      lexicalAfter === null ||\n      lexicalAfter.isSymbolicLink() ||\n      !lexicalAfter.isFile() ||\n      !sameIdentity(fileIdentity(lexicalBefore), fileIdentity(lexicalAfter)) ||\n      !sameIdentity(fileIdentity(lexicalAfter), afterIdentity) ||\n      !sameIdentity(beforeIdentity, afterIdentity)\n    ) {\n      throw new Error(\n        `ACPX ${agent} runtime executable changed while it was verified`,\n      );\n    }\n    const digest = `sha256:${hash.digest(\"hex\")}`;\n    if (digest !== expectedDigest) {\n      throw new Error(`ACPX ${agent} runtime executable digest mismatch`);\n    }\n    return { handle, identity: afterIdentity };\n  } catch (error) {\n    await handle.close();\n    throw error;\n  }\n}\n\n/** Fail closed where Node cannot atomically refuse a final symlink component. */\nexport function verifiedExecutableOpenFlags(\n  platform: NodeJS.Platform,\n  noFollowFlag: number | undefined,\n): number {\n  if (\n    platform === \"win32\" ||\n    typeof noFollowFlag !== \"number\" ||\n    noFollowFlag === 0\n  ) {","sourceCodeStart":1076,"sourceCodeEnd":1112,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts#L1076-L1112","documentation":"After a race-free hash is computed, the sha256 digest of the executable is compared to the `expectedDigest` the runtime was provisioned with. A mismatch means the on-disk binary is not the exact bytes the installation recorded — corrupt, tampered with, or a different version. Execution is refused to guarantee supply-chain integrity of the ACPX runtime.","triggerScenarios":"`sha256:${hash.digest('hex')}` of the opened executable differs from the expectedDigest recorded at install/verify time: the binary was modified in place, replaced by a different version, corrupted by disk/transfer errors, or the recorded digest is stale (e.g. manifest from a different release).","commonSituations":"Manual edits or patching of the installed binary; downloading from a mirror that served different bytes; disk corruption or incomplete copy; version skew where the installer wrote a digest for release X but release Y was extracted; users replacing the binary with a wrapper script of the same name.","solutions":["Reinstall the ACPX runtime via its official installer so bytes and recorded digest match again","Confirm the expectedDigest/manifest matches the release actually installed (version skew check)","Verify your download integrity independently (checksum the source archive) to rule out a corrupt transfer","Stop modifying/patching the installed binary in place; apply patches through the packaging pipeline and regenerate the digest","Check for disk errors (fsck/SMART) if corruption recurs across reinstalls"],"exampleFix":"// before: stale manifest from v1.2 against v1.3 binary\nexpectedDigest: 'sha256:abc123...'  # digest of 1.2\n// after: reinstall + regenerate manifest\nacpx-installer install --version 1.3\nmanifest.digest = sha256File('/opt/acpx/bin/acpx')  # sha256:def456...","handlingStrategy":"try-catch","validationCode":"import { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nasync function sha256File(p: string): Promise<string> {\n  const h = createHash('sha256');\n  for await (const chunk of createReadStream(p)) h.update(chunk);\n  return `sha256:${h.digest('hex')}`;\n}\n// compare sha256File(executablePath) against the recorded digest before starting","typeGuard":null,"tryCatchPattern":"try {\n  await startAcppRuntime();\n} catch (e) {\n  if (String(e?.message).includes('digest mismatch')) {\n    // do NOT retry against the same bytes; reinstall first\n    await reinstallAcpxRuntime(expectedVersion);\n    await startAcppRuntime();\n  } else {\n    throw e;\n  }\n}","preventionTips":["Never edit or patch the installed binary in place; regenerate the digest through the packaging pipeline","Keep the digest manifest and the binary in lockstep (same release/version)","Verify download checksums at install time before recording the digest","On mismatch, reinstall; never regenerate the digest to accept arbitrary bytes"],"tags":["security","integrity","checksum","acpx"],"backgroundTag":"checksum-mismatch","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"}