{"record":{"id":"d84e5e2a38b98a19","repo":"paperclipai/paperclip","slug":"acpx-provider-runtime-executable-count-is-invalid","errorCode":null,"errorMessage":"ACPX provider runtime executable count is invalid","messagePattern":"ACPX provider runtime executable count is invalid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts","lineNumber":142,"sourceCode":"owner.once(\"end\", reap);\nowner.once(\"error\", reap);\nowner.resume();\ntry {\n  fs.writeSync(4, \"armed\\\\n\");\n} catch {\n  reap();\n}\n`;\n\nexport const PROVIDER_LIFETIME_GUARDIAN_SOURCE = `\nconst fs = require(\"node:fs\");\nconst { spawn } = require(\"node:child_process\");\nconst WATCHDOG_SOURCE = ${JSON.stringify(PROVIDER_LIFETIME_WATCHDOG_SOURCE)};\nconst runtimeExecutable = process.env.${VERIFIED_RUNTIME_EXECUTABLE_ENV} || process.execPath;\nconst dependencyAncestorCount = Number.parseInt(process.argv[4], 10);\nconst providerRuntimeExecutableCount = Number.parseInt(process.argv[8], 10);\nif (!Number.isSafeInteger(dependencyAncestorCount) || dependencyAncestorCount < 0 || dependencyAncestorCount > ${MAX_DEPENDENCY_ANCESTORS}) throw new Error(\"ACPX provider dependency ancestry is invalid\");\nif (providerRuntimeExecutableCount !== 0 && providerRuntimeExecutableCount !== 1) throw new Error(\"ACPX provider runtime executable count is invalid\");\nconst PROVIDER_RUNTIME_EXECUTABLE_FD = ${DEPENDENCY_ANCESTOR_FD_START} + dependencyAncestorCount;\nconst OWNER_FD = PROVIDER_RUNTIME_EXECUTABLE_FD + providerRuntimeExecutableCount;\nconst OWNERSHIP_FD = OWNER_FD + 1;\nconst PROVIDER_EXIT_FD = OWNERSHIP_FD + 1;\nconst CREDENTIAL_FENCE_FD_START = PROVIDER_EXIT_FD + 1;\nconst VERIFIED_RUNTIME_FD = CREDENTIAL_FENCE_FD_START + 2;\nconst dependencyAncestorFds = Array.from({ length: dependencyAncestorCount }, (_, index) => ${DEPENDENCY_ANCESTOR_FD_START} + index);\nconst runtimeDescriptorMatch = /^\\\\/proc\\\\/self\\\\/fd\\\\/([0-9]+)$/.exec(runtimeExecutable);\nconst runtimeDescriptorFd = runtimeDescriptorMatch === null ? null : Number.parseInt(runtimeDescriptorMatch[1], 10);\nif (runtimeDescriptorFd !== null && runtimeDescriptorFd !== VERIFIED_RUNTIME_FD) throw new Error(\"ACPX verified runtime descriptor is misplaced\");\nif (runtimeDescriptorFd !== null) fs.fstatSync(runtimeDescriptorFd);\nlet provider;\nlet watchdog;\nlet reaped = false;\nlet shutdownStarted = false;\nconst reap = () => {\n  if (reaped) return;\n  reaped = true;","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts#L124-L160","documentation":"Also thrown by the generated provider-lifetime watchdog script: process.argv[8] (providerRuntimeExecutableCount) must be exactly 0 or 1, because the watchdog's FD layout adds either zero or one descriptor for a provider runtime executable between the dependency-ancestor FDs and OWNER_FD. Any other value makes the FD arithmetic ambiguous, so the script throws immediately.","triggerScenarios":"The spawning parent passes argv[8] as anything other than '0' or '1' — e.g. a boolean string ('true'), an undefined coerced to NaN via parseInt, the wrong argv slot after a refactor, or a computed count like the number of executables when the layout only supports 0 or 1.","commonSituations":"A code change added a second provider runtime executable without updating the watchdog layout; argument-position drift in the spawn call after inserting a new argv entry; passing a raw boolean/flag instead of the normalized 0/1 count; environment detection returning an unexpected executable count.","solutions":["Inspect the spawn call and ensure argv[8] is exactly '0' or '1', derived via something like providerRuntimeExecutable ? 1 : 0.","If a second provider runtime executable is genuinely required, update the generated watchdog layout to support count > 1 instead of passing the raw count.","Confirm no refactor inserted/removed argv entries ahead of position 8, shifting the value the watchdog reads.","Add a parent-side assertion (count === 0 || count === 1) before spawning to fail fast with a clearer message."],"exampleFix":"// before: raw count may be >1 or non-numeric\nspawn(process.execPath, [watchdogEntry, ..., String(runtimeExecutables.length), ...]);\n\n// after: normalize to 0/1\nconst providerRuntimeExecutableCount = runtimeExecutables.length > 0 ? 1 : 0;\nspawn(process.execPath, [watchdogEntry, ..., String(providerRuntimeExecutableCount), ...]);","handlingStrategy":"validation","validationCode":"const providerRuntimeExecutableCount = runtimeExecutables.length > 0 ? 1 : 0;\nif (providerRuntimeExecutableCount !== 0 && providerRuntimeExecutableCount !== 1) {\n  throw new Error('provider runtime executable count must be 0 or 1');\n}\n// pass String(providerRuntimeExecutableCount) at argv position 8","typeGuard":"function isBinaryCount(n: unknown): n is 0 | 1 {\n  return n === 0 || n === 1;\n}","tryCatchPattern":"try {\n  await spawnProviderWatchdog(args);\n} catch (err) {\n  if (err.message.includes('runtime executable count is invalid')) {\n    // normalize the count and retry once\n    args[8] = String(runtimeExecutables.length > 0 ? 1 : 0);\n    await spawnProviderWatchdog(args);\n  } else throw err;\n}","preventionTips":["Normalize the executable count to 0/1 at the spawn site rather than passing raw array lengths or booleans.","Write a regression test that executes the generated watchdog script with boundary argv values (0 and 1).","When changing the spawn argv layout, audit every argv[N] index consumed by the generated script in the same PR."],"tags":["watchdog","argv","validation","process-spawn","acpx"],"backgroundTag":"invalid-argument-value","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"}