{"record":{"id":"0508f3e5e313a503","repo":"stablyai/orca","slug":"benchmark-process-group-signal-recovery-failed","errorCode":null,"errorMessage":"Benchmark process group signal recovery failed","messagePattern":"Benchmark process group signal recovery failed","errorType":"exception","errorClass":"AggregateError","httpStatus":null,"severity":"error","filePath":"config/scripts/macos-computer-helper-owner-loss-processes.mjs","lineNumber":309,"sourceCode":"    if (\n      stoppedMembers.length === 0 ||\n      stoppedMembers.some((identity) => !identity.command.includes(environmentFragment))\n    ) {\n      throw new Error('Benchmark process group changed before signaling')\n    }\n    if (signal !== 'SIGSTOP') {\n      operations.signalProcess(-pgid, signal)\n      if (signal !== 'SIGKILL') {\n        operations.signalProcess(-pgid, 'SIGCONT')\n      }\n      groupState.stopped = false\n      groupState.anchorPid = null\n    }\n    return true\n  } catch (error) {\n    const recoveryErrors = compensateStoppedGroup(pgid, groupState, operations)\n    if (recoveryErrors.length > 0) {\n      throw new AggregateError(\n        [error, ...recoveryErrors],\n        'Benchmark process group signal recovery failed'\n      )\n    }\n    if (error.code === 'ESRCH') {\n      return false\n    }\n    throw error\n  }\n}\n\nexport function writeProcessRecord(recordPath, processIdentity) {\n  const temporaryPath = `${recordPath}.${process.pid}.tmp`\n  writeFileSync(temporaryPath, JSON.stringify(processIdentity))\n  renameSync(temporaryPath, recordPath)\n}\n\nexport function processIdentityIsCurrent(identity) {","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/macos-computer-helper-owner-loss-processes.mjs#L291-L327","documentation":"Outer catch of signalValidatedProcessGroup: the main signaling try-block threw, then compensateStoppedGroup (which SIGCONTs both the stopped group and any anchor pid) itself returned one or more non-ESRCH errors. The original error and every recovery error are combined into an AggregateError so neither is hidden.","triggerScenarios":"Any throw from the anchor-stop or group-signal path (141, 142, or a raw signal error) AND compensateStoppedGroup records errors — i.e. SIGCONT of -pgid or the anchor throws something other than ESRCH.","commonSituations":"The owner-loss scenario again: by recovery time the helper's uid has changed, so both the original signal and the resume raise EPERM. Also triggered by tests that inject a signalProcess stub failing on every call.","solutions":["Inspect AggregateError.errors — the first element is the root cause, the rest are recovery failures","After this throw, fall back to killProcessMatchingCommand(environmentFragment) to sweep stray helpers","Ensure compensateStoppedGroup treats EPERM on a uid-changed process as non-fatal if the trial is already being abandoned","Reproduce with the operations parameter injected to log every signal and its target identity"],"exampleFix":"// before\ntry {\n  signalValidatedProcessGroup(pgid, frag, sig, state)\n} catch (error) {\n  console.error(error.message)\n}\n\n// after\ntry {\n  signalValidatedProcessGroup(pgid, frag, sig, state)\n} catch (error) {\n  const causes = error instanceof AggregateError ? error.errors : [error]\n  for (const cause of causes) console.error(cause)\n  killProcessMatchingCommand([frag])\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isAggregateError(e) {\n  return e instanceof Error && Array.isArray(e.errors)\n}","tryCatchPattern":"try {\n  signalValidatedProcessGroup(pgid, frag, sig, state)\n} catch (error) {\n  const causes = error instanceof AggregateError ? error.errors : [error]\n  // causes[0] is the original; causes.slice(1) are recovery failures\n  for (const c of causes) log.error(c)\n  killProcessMatchingCommand([frag])\n}","preventionTips":["Always pair signalValidatedProcessGroup with a killProcessMatchingCommand fallback","Inspect AggregateError.errors in order: root cause first, recovery failures after","In tests, inject operations to assert the recovery path is exercised"],"tags":["process-management","error-recovery","aggregate","signals"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}