{"record":{"id":"ce5fa26a1150f583","repo":"stablyai/orca","slug":"sidecar-did-not-exit-after-graceful-ipc-close","errorCode":null,"errorMessage":"Sidecar did not exit after graceful IPC close","messagePattern":"Sidecar did not exit after graceful IPC close","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"config/scripts/macos-computer-helper-owner-loss-benchmark.mjs","lineNumber":313,"sourceCode":"    latencies.push(performance.now() - requestStartedAt)\n  }\n  const totalMs = performance.now() - startedAt\n  return {\n    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 {","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/macos-computer-helper-owner-loss-benchmark.mjs#L295-L331","documentation":"verifyGracefulClose disconnects the sidecar's IPC channel and expects the sidecar process to exit within PROCESS_EXIT_TIMEOUT_MS. If it does not, the sidecar is hanging on a non-IPC reference (open handle, blocked syscall) instead of reacting to channel close — a graceful-shutdown regression. The helper-exit check (119) runs only after this passes.","triggerScenarios":"The sidecar ignores the IPC 'disconnect' event, holds a keep-alive handle (timer/socket/stdio) that prevents exit, or is blocked in a synchronous operation when disconnect fires.","commonSituations":"A code change that added an unref'd-but-still-referenced timer, an open file/socket keeping the event loop alive, or a regression in the disconnect handler that previously called process.exit.","solutions":["Inspect the sidecar for lingering handles (node --trace-warnings / process._getActiveHandles) when run standalone.","Confirm the sidecar's IPC disconnect handler still triggers exit (e.g. calls server.close/process.exit).","If the helper genuinely needs more time, raise PROCESS_EXIT_TIMEOUT_MS — but only after ruling out a real hang.","Check that stdio to the parent is closed/ignored so it does not keep the loop alive."],"exampleFix":"// before\nsidecar.child.disconnect()\nif (!(await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS))) {\n  throw new Error('Sidecar did not exit after graceful IPC close')\n}\n\n// after\nsidecar.child.disconnect()\nif (!(await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS))) {\n  const handles = await tryGetActiveHandles(sidecar)\n  throw new Error(`Sidecar did not exit after graceful IPC close; activeHandles=${JSON.stringify(handles)}`)\n}","handlingStrategy":"retry","validationCode":"const exited = await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS)\nif (!exited) {\n  throw new Error(`Sidecar still alive ${PROCESS_EXIT_TIMEOUT_MS}ms after disconnect; inspect active handles`)\n}","typeGuard":"const hasExited = (child) => child.exitCode !== null || child.signalCode !== null","tryCatchPattern":"try {\n  await verifyGracefulClose()\n} catch (e) {\n  if (/Sidecar did not exit/.test(e.message)) { /* SIGTERM fallback then re-check */ sidecar.child.kill('SIGTERM'); await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS) }\n  else throw e\n}","preventionTips":["Ensure the sidecar's IPC disconnect handler calls process.exit/server.close.","Avoid unref'd-but-referenced timers/sockets that keep the event loop alive.","Close/ignore stdio to the parent so it doesn't hold the loop open."],"tags":["process","graceful-shutdown","timeout","sidecar"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}