stablyai/orca · error · Error

Helper did not exit after graceful owner close

Error message

Helper did not exit after graceful owner close

What it means

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.

Source

Thrown at config/scripts/macos-computer-helper-owner-loss-benchmark.mjs:317

    totalMs,
    requestsPerSecond: (ACTIVE_REQUEST_COUNT * 1_000) / totalMs,
    medianLatencyMs: median(latencies),
    p95LatencyMs: percentile(latencies, 0.95),
    maxLatencyMs: Math.max(...latencies)
  }
}

async function verifyGracefulClose() {
  const { sidecar, helper } = await startAuthenticatedSession()
  try {
    const startedAt = performance.now()
    sidecar.child.disconnect()
    if (!(await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS))) {
      throw new Error('Sidecar did not exit after graceful IPC close')
    }
    const helperExitMs = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)
    if (helperExitMs === null) {
      throw new Error('Helper did not exit after graceful owner close')
    }
    return Math.round(performance.now() - startedAt)
  } finally {
    sidecar.child.kill('SIGKILL')
    await stopProcess(helper)
  }
}

async function runInternalTrial(expectation) {
  let sidecar
  let helper
  let invalidPeer
  let invalidPeerRejected = false
  try {
    const session = await startAuthenticatedSession()
    sidecar = session.sidecar
    helper = session.helper
    const authenticatedAt = performance.now()

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Confirm the helper's owner-loss mechanism (parent-pid monitor / pipe-to-parent EOF) is installed and firing.
  2. Raise PROCESS_EXIT_TIMEOUT_MS only after verifying the helper does exit eventually (a slow but correct detector).
  3. Check for a regression where the helper's signal handler was removed or its parent-pid capture broke.
  4. Reproduce standalone: spawn sidecar, kill it, observe whether the helper pid disappears.

Example fix

// before
const helperExitMs = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)
if (helperExitMs === null) {
  throw new Error('Helper did not exit after graceful owner close')
}

// after
const helperExitMs = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)
if (helperExitMs === null) {
  throw new Error(`Helper pid=${helper.pid} did not exit after graceful owner close within ${PROCESS_EXIT_TIMEOUT_MS}ms`)
}
Defensive patterns

Strategy: retry

Validate before calling

const ms = await waitForProcessExit(helper, PROCESS_EXIT_TIMEOUT_MS)
if (ms === null) {
  throw new Error(`Helper pid=${helper.pid} orphaned after owner exit within ${PROCESS_EXIT_TIMEOUT_MS}ms`)
}

Type guard

const isOrphanedHelper = (ms) => ms === null

Try / catch

try {
  await verifyGracefulClose()
} catch (e) {
  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 }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/23bb83bf5e39ff55. Report an issue: GitHub.