{"record":{"id":"76102e85893fc3ea","repo":"stablyai/orca","slug":"expected-helper-to-exit-after-abrupt-authenticated","errorCode":null,"errorMessage":"Expected helper to exit after abrupt authenticated owner loss","messagePattern":"Expected helper to exit after abrupt authenticated owner loss","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"config/scripts/macos-computer-helper-owner-loss-benchmark.mjs","lineNumber":355,"sourceCode":"    const activeRequests = await exerciseActiveRequests(sidecar)\n    const remainingHoldMs = Math.max(0, OWNER_HOLD_MS - (performance.now() - authenticatedAt))\n    await sleep(remainingHoldMs)\n    const connected = await sampleProcess(helper.pid)\n    const invalidSocketPath = socketPathFromCommand(helper.command)\n    invalidPeer = await connectInvalidPeer(invalidSocketPath)\n    invalidPeerRejected = true\n    const survivedClaimDeadline =\n      performance.now() - authenticatedAt >= OWNER_HOLD_MS && isProcessAlive(helper.pid)\n\n    sidecar.child.kill('SIGKILL')\n    await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS)\n    const abruptExitMs = await waitForProcessExit(\n      helper,\n      expectation === 'reaped' ? PROCESS_EXIT_TIMEOUT_MS : RETAIN_PROOF_MS\n    )\n    const helperExitedAfterAbruptLoss = abruptExitMs !== null\n    if (expectation === 'reaped' && !helperExitedAfterAbruptLoss) {\n      throw new Error('Expected helper to exit after abrupt authenticated owner loss')\n    }\n    if (expectation === 'retained' && helperExitedAfterAbruptLoss) {\n      throw new Error('Expected baseline helper to remain after abrupt owner loss')\n    }\n    const postLossRssBytes = helperExitedAfterAbruptLoss ? 0 : processSnapshot(helper.pid).rssBytes\n    await stopProcess(helper)\n    helper = null\n    invalidPeer.destroy()\n    invalidPeer = null\n\n    const gracefulExitMs = await verifyGracefulClose()\n    return {\n      authenticated: session.authenticated,\n      survivedClaimDeadline,\n      invalidPeerRejectedAndDidNotRetain: invalidPeerRejected && helperExitedAfterAbruptLoss,\n      connectedRssBytes: connected.rssBytes,\n      connectedCpuMilliseconds: Math.max(\n        0,","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/macos-computer-helper-owner-loss-benchmark.mjs#L337-L373","documentation":"Thrown by the owner-loss benchmark when running with --expect reaped. After SIGKILL-ing the authenticated sidecar (the helper's owner), the benchmark polls the helper for up to PROCESS_EXIT_TIMEOUT_MS (5s). If the helper is still alive after that window, the owner-loss reaping security contract is violated: a privileged helper must not outlive its authenticated owner.","triggerScenarios":"Called from runInternalTrial('reaped') at line 354. The sidecar is killed via sidecar.child.kill('SIGKILL'), then waitForProcessExit(helper, 5000) returns null because processIdentityIsCurrent still matches after 5s of 50ms polling. helperExitedAfterAbruptLoss is false, so the assertion fires.","commonSituations":"The helper's owner-death watchdog (kqueue/pidfd/poll on the sidecar PID) is broken or its poll interval exceeds the 5s window; the helper was spawned detached or re-parented to launchd so it never observes the sidecar exit; the helper's process group differs from the sidecar's so SIGKILL does not cascade; a regression in the helper lifecycle manager prevents self-termination.","solutions":["Inspect the helper's owner-loss detection: confirm it monitors the sidecar PID and exits within 5s (check kqueue/dispatch source registration or poll interval)","Verify the helper's ppid is the sidecar and it is not detached — run ps -o pid,ppid,pgid,command on the helper mid-trial","Run --expect retained first to confirm setup (sidecar launch, helper spawn, authentication handshake) is correct, isolating the reaping logic from setup failures","Inspect the trial's stderr.log in the launcher temp dir for unhandled helper errors that block clean shutdown"],"exampleFix":"// before: helper polls owner every 10s — misses the 5s reaping deadline\nsetInterval(() => { if (!isOwnerAlive(sidecarPid)) process.exit(0) }, 10_000)\n\n// after: use macOS dispatch source / kqueue for immediate exit notification\nconst source = watchProcessExit(sidecarPid)\nsource.on('exit', () => process.exit(0))","handlingStrategy":"try-catch","validationCode":"// Before running the benchmark, verify the helper will detect owner loss\nconst helper = await waitForHelper(sidecarPid)\n// confirm helper is a child of the sidecar, not detached\nconst ps = execFileSync('ps', ['-o', 'pid,ppid,pgid,command', '-p', String(helper.pid)], { encoding: 'utf8' })\nif (!ps.includes(String(sidecarPid))) {\n  throw new Error('Helper is not a child of the sidecar — reaping will fail')\n}","typeGuard":null,"tryCatchPattern":"try {\n  const result = await runInternalTrial('reaped')\n} catch (error) {\n  if (error.message.includes('Expected helper to exit')) {\n    // owner-loss reaping failed — inspect helper logs, check watchdog implementation\n    console.error('Owner-loss reaping contract violated:', error)\n  }\n  throw error\n}","preventionTips":["Test the helper's owner-loss detection in isolation before running the full benchmark","Ensure the helper monitors the sidecar PID with sub-second latency (kqueue/dispatch source, not long polls)","Verify the helper is a child of the sidecar, not re-parented to launchd"],"tags":["macos","process-lifecycle","owner-loss","benchmark","security"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}