stablyai/orca · error · Error

Watcher supervisor did not replace the faulted child

Error message

Watcher supervisor did not replace the faulted child

What it means

After sending SIGSEGV to firstChildPid and awaiting the interrupted/replacement event, the harness reads supervisor.child?.pid again. It expects a NEW pid (different from firstChildPid) — proving the supervisor auto-respawned the crashed watcher. If the pid is missing or unchanged, the supervisor's self-healing contract is broken and the test fails.

Source

Thrown at config/scripts/runtime-file-watcher-fault-harness.mjs:139

      (listener) => {
        eventListener = listener
      },
      (event) => event.path === join(rootPath, 'before.txt'),
      'pre-crash watch event'
    )
    await writeFile(join(rootPath, 'before.txt'), 'before')
    await Promise.race([beforeEvent, watcherError])

    const firstChildPid = supervisor.child?.pid
    if (!firstChildPid) {
      throw new Error('Watcher supervisor did not expose a live child')
    }
    process.kill(firstChildPid, 'SIGSEGV')
    await Promise.race([interrupted, watcherError])

    const replacementChildPid = supervisor.child?.pid
    if (!replacementChildPid || replacementChildPid === firstChildPid) {
      throw new Error('Watcher supervisor did not replace the faulted child')
    }
    const afterEvent = nextMatchingEvent(
      (listener) => {
        eventListener = listener
      },
      (event) => event.path === join(rootPath, 'after.txt'),
      'post-crash watch event'
    )
    await writeFile(join(rootPath, 'after.txt'), 'after')
    await Promise.race([afterEvent, watcherError])

    console.log(
      JSON.stringify({
        hostPid: process.pid,
        killedWatcherPid: firstChildPid,
        replacementWatcherPid: replacementChildPid,
        hostSurvived: true,
        automaticResubscribe: true,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Confirm the SIGSEGV was sent to the child pid, not the supervisor pid (the harness uses firstChildPid).
  2. Await the supervisor's respawn promise/event (the harness races interrupted vs watcherError — extend the race to include the respawn signal).
  3. Re-enable/fix the supervisor's auto-respawn logic; this assertion exists precisely to catch its removal.
  4. If the supervisor intentionally does not respawn, this test is N/A — skip it on that code path.

Example fix

// before
process.kill(firstChildPid, 'SIGSEGV')
await Promise.race([interrupted, watcherError])
const replacementChildPid = supervisor.child?.pid
// after
process.kill(firstChildPid, 'SIGSEGV')
await supervisor.waitForRespawn()  // explicit respawn signal
const replacementChildPid = supervisor.child?.pid
Defensive patterns

Strategy: validation

Validate before calling

const replacement = supervisor.child?.pid
if (typeof replacement !== 'number' || replacement === firstChildPid) {
  console.error('Supervisor did not respawn the faulted child'); process.exit(1)
}

Type guard

function isRespawned(next: number | undefined, prev: number): next is number {
  return typeof next === 'number' && next !== prev
}

Try / catch

if (!isRespawned(supervisor.child?.pid, firstChildPid)) {
  throw new Error('Watcher supervisor did not replace the faulted child')
}

Prevention

When it happens

Trigger: The supervisor did not respawn the child after SIGSEGV (no recovery logic), respawned but reused the same pid (impossible normally), or the replacement race window closed before the read.

Common situations: A regression in WatcherProcessSupervisor that removed/bugged the respawn-on-crash path; the SIGSEGV killed the supervisor itself instead of just the child; respawn is async and the read happened before it completed; crash backoff disabled.

Related errors


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