stablyai/orca · error · Error
Expected helper to exit after abrupt authenticated owner los
Error message
Expected helper to exit after abrupt authenticated owner loss
What it means
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.
Source
Thrown at config/scripts/macos-computer-helper-owner-loss-benchmark.mjs:355
const activeRequests = await exerciseActiveRequests(sidecar)
const remainingHoldMs = Math.max(0, OWNER_HOLD_MS - (performance.now() - authenticatedAt))
await sleep(remainingHoldMs)
const connected = await sampleProcess(helper.pid)
const invalidSocketPath = socketPathFromCommand(helper.command)
invalidPeer = await connectInvalidPeer(invalidSocketPath)
invalidPeerRejected = true
const survivedClaimDeadline =
performance.now() - authenticatedAt >= OWNER_HOLD_MS && isProcessAlive(helper.pid)
sidecar.child.kill('SIGKILL')
await waitForChildExit(sidecar.child, PROCESS_EXIT_TIMEOUT_MS)
const abruptExitMs = await waitForProcessExit(
helper,
expectation === 'reaped' ? PROCESS_EXIT_TIMEOUT_MS : RETAIN_PROOF_MS
)
const helperExitedAfterAbruptLoss = abruptExitMs !== null
if (expectation === 'reaped' && !helperExitedAfterAbruptLoss) {
throw new Error('Expected helper to exit after abrupt authenticated owner loss')
}
if (expectation === 'retained' && helperExitedAfterAbruptLoss) {
throw new Error('Expected baseline helper to remain after abrupt owner loss')
}
const postLossRssBytes = helperExitedAfterAbruptLoss ? 0 : processSnapshot(helper.pid).rssBytes
await stopProcess(helper)
helper = null
invalidPeer.destroy()
invalidPeer = null
const gracefulExitMs = await verifyGracefulClose()
return {
authenticated: session.authenticated,
survivedClaimDeadline,
invalidPeerRejectedAndDidNotRetain: invalidPeerRejected && helperExitedAfterAbruptLoss,
connectedRssBytes: connected.rssBytes,
connectedCpuMilliseconds: Math.max(
0,View on GitHub (pinned to 1136503c6a)
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
Example fix
// before: helper polls owner every 10s — misses the 5s reaping deadline
setInterval(() => { if (!isOwnerAlive(sidecarPid)) process.exit(0) }, 10_000)
// after: use macOS dispatch source / kqueue for immediate exit notification
const source = watchProcessExit(sidecarPid)
source.on('exit', () => process.exit(0)) Defensive patterns
Strategy: try-catch
Validate before calling
// Before running the benchmark, verify the helper will detect owner loss
const helper = await waitForHelper(sidecarPid)
// confirm helper is a child of the sidecar, not detached
const ps = execFileSync('ps', ['-o', 'pid,ppid,pgid,command', '-p', String(helper.pid)], { encoding: 'utf8' })
if (!ps.includes(String(sidecarPid))) {
throw new Error('Helper is not a child of the sidecar — reaping will fail')
} Try / catch
try {
const result = await runInternalTrial('reaped')
} catch (error) {
if (error.message.includes('Expected helper to exit')) {
// owner-loss reaping failed — inspect helper logs, check watchdog implementation
console.error('Owner-loss reaping contract violated:', error)
}
throw error
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Expected baseline helper to remain after abrupt owner loss
- The production watchdog is macOS-only; run this benchmark on
- Could not find helper owned by sidecar ${sidecarPid}
- Benchmark pending anchor recovery failed
- Could not read macOS Keychain item ${service}/${account}.
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/76102e85893fc3ea.
Report an issue: GitHub.