stablyai/orca · error · AggregateError

Benchmark helper signal recovery failed

Error message

Benchmark helper signal recovery failed

What it means

Catch block of signalProcessIdentity: the main signal try-block threw, the helper had been SIGSTOP'd (stopped === true), and the SIGCONT recovery (operations.signalProcess(identity.pid, 'SIGCONT')) also threw a non-ESRCH error. Original plus resume error are wrapped in an AggregateError so the resume failure is not lost.

Source

Thrown at config/scripts/macos-computer-helper-owner-loss-processes.mjs:376

    operations.signalProcess(-identity.pgid, signal)
    if (signal !== 'SIGKILL') {
      operations.signalProcess(-identity.pgid, 'SIGCONT')
    }
    stopped = false
    return true
  } catch (error) {
    let resumeError
    if (stopped) {
      try {
        operations.signalProcess(identity.pid, 'SIGCONT')
      } catch (caught) {
        if (caught?.code !== 'ESRCH') {
          resumeError = caught
        }
      }
    }
    if (resumeError) {
      throw new AggregateError([error, resumeError], 'Benchmark helper signal recovery failed')
    }
    if (error.code === 'ESRCH') {
      return false
    }
    throw error
  }
}

export function killRecordedProcess(recordPath, expectedCommandFragment) {
  if (!existsSync(recordPath)) {
    return false
  }
  const record = JSON.parse(readFileSync(recordPath, 'utf8'))
  if (!signalProcessIdentity(record, expectedCommandFragment, 'SIGKILL')) {
    return false
  }
  return waitForIdentityExit(record)
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Unpack AggregateError.errors: [0] is the original signal failure, [1] is the resume failure
  2. After this throw, run killProcessMatchingCommand to sweep the helper by command fragment
  3. Make the resume path tolerate EPERM when the trial is already aborting
  4. Inject operations in tests to assert both signal and resume are exercised
Defensive patterns

Strategy: try-catch

Type guard

function isAggregateError(e) {
  return e instanceof Error && Array.isArray(e.errors)
}

Try / catch

try {
  signalProcessIdentity(recorded, frag, sig)
} catch (error) {
  const causes = error instanceof AggregateError ? error.errors : [error]
  // causes[0] = original signal error, causes[1] = resume error
  killProcessMatchingCommand([frag])
}

Prevention

When it happens

Trigger: operations.signalProcess(-identity.pgid, signal) throws, stopped is true, and the subsequent SIGCONT of identity.pid raises a non-ESRCH error (EPERM/EINVAL).

Common situations: Owner-loss again: by resume time the helper's uid changed. Also: signalProcess stubbed to always throw, or the pgid became invalid because the process group leader exited.

Related errors


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