{"record":{"id":"23bb83bf5e39ff55","repo":"stablyai/orca","slug":"helper-did-not-exit-after-graceful-owner-close","errorCode":null,"errorMessage":"Helper did not exit after graceful owner close","messagePattern":"Helper did not exit after graceful owner close","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"config/scripts/macos-computer-helper-owner-loss-benchmark.mjs","lineNumber":317,"sourceCode":"    totalMs,\n    requestsPerSecond: (ACTIVE_REQUEST_COUNT * 1_000) / totalMs,\n    medianLatencyMs: median(latencies),\n    p95LatencyMs: percentile(latencies, 0.95),\n    maxLatencyMs: Math.max(...latencies)\n  }\n}\n\nasync function verifyGracefulClose() {\n  const { sidecar, helper } = await startAuthenticatedSession()\n  try {\n    const startedAt = performance.now()\n    sidecar.child.disconnect()\n    if (!(await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS))) {\n      throw new Error('Sidecar did not exit after graceful IPC close')\n    }\n    const helperExitMs = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)\n    if (helperExitMs === null) {\n      throw new Error('Helper did not exit after graceful owner close')\n    }\n    return Math.round(performance.now() - startedAt)\n  } finally {\n    sidecar.child.kill('SIGKILL')\n    await stopProcess(helper)\n  }\n}\n\nasync function runInternalTrial(expectation) {\n  let sidecar\n  let helper\n  let invalidPeer\n  let invalidPeerRejected = false\n  try {\n    const session = await startAuthenticatedSession()\n    sidecar = session.sidecar\n    helper = session.helper\n    const authenticatedAt = performance.now()","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/macos-computer-helper-owner-loss-benchmark.mjs#L299-L335","documentation":"After the sidecar exits (118 passed), the helper — spawned as a child of the sidecar — must also exit within PROCESS_EXIT_TIMEOUT_MS. waitForProcessExit returning null means the helper outlived its owner, i.e. the owner-loss/orphan behavior this benchmark exists to catch. This is the core regression signal for helper owner-loss handling.","triggerScenarios":"The helper does not monitor its owner (parent pid) and so keeps running as an orphan after the sidecar dies, or its owner-loss detector is too slow and exceeds the timeout.","commonSituations":"A regression removing/disabling the helper's parent-death watch (e.g. prctl PR_SET_PDEATHSIG on macOS equivalent, or pidfd/poll), the helper blocked in a syscall that ignores the signal, or a slower machine making the detector miss the window.","solutions":["Confirm the helper's owner-loss mechanism (parent-pid monitor / pipe-to-parent EOF) is installed and firing.","Raise PROCESS_EXIT_TIMEOUT_MS only after verifying the helper does exit eventually (a slow but correct detector).","Check for a regression where the helper's signal handler was removed or its parent-pid capture broke.","Reproduce standalone: spawn sidecar, kill it, observe whether the helper pid disappears."],"exampleFix":"// before\nconst helperExitMs = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)\nif (helperExitMs === null) {\n  throw new Error('Helper did not exit after graceful owner close')\n}\n\n// after\nconst helperExitMs = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)\nif (helperExitMs === null) {\n  throw new Error(`Helper pid=${helper.pid} did not exit after graceful owner close within ${PROCESS_EXIT_TIMEOUT_MS}ms`)\n}","handlingStrategy":"retry","validationCode":"const ms = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)\nif (ms === null) {\n  throw new Error(`Helper pid=${helper.pid} orphaned after owner exit within ${PROCESS_EXIT_TIMEOUT_MS}ms`)\n}","typeGuard":"const isOrphanedHelper = (ms) => ms === null","tryCatchPattern":"try {\n  await verifyGracefulClose()\n} catch (e) {\n  if (/Helper did not exit/.test(e.message)) { /* this is the regression under test — do not silently retry; report and kill */ helper.pid && process.kill(helper.pid, 'SIGKILL'); throw e }\n  throw e\n}","preventionTips":["Ensure the helper installs an owner-loss watch (parent-pid poll / pipe-EOF) at startup.","Reproduce standalone (spawn sidecar, kill it, watch helper pid) before bisecting.","Treat this failure as a real regression, not transient — do not paper over with retries."],"tags":["process","orphan","graceful-shutdown","helper","owner-loss"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}