stablyai/orca · error · AggregateError

Benchmark process group signal recovery failed

Error message

Benchmark process group signal recovery failed

What it means

Outer catch of signalValidatedProcessGroup: the main signaling try-block threw, then compensateStoppedGroup (which SIGCONTs both the stopped group and any anchor pid) itself returned one or more non-ESRCH errors. The original error and every recovery error are combined into an AggregateError so neither is hidden.

Source

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

    if (
      stoppedMembers.length === 0 ||
      stoppedMembers.some((identity) => !identity.command.includes(environmentFragment))
    ) {
      throw new Error('Benchmark process group changed before signaling')
    }
    if (signal !== 'SIGSTOP') {
      operations.signalProcess(-pgid, signal)
      if (signal !== 'SIGKILL') {
        operations.signalProcess(-pgid, 'SIGCONT')
      }
      groupState.stopped = false
      groupState.anchorPid = null
    }
    return true
  } catch (error) {
    const recoveryErrors = compensateStoppedGroup(pgid, groupState, operations)
    if (recoveryErrors.length > 0) {
      throw new AggregateError(
        [error, ...recoveryErrors],
        'Benchmark process group signal recovery failed'
      )
    }
    if (error.code === 'ESRCH') {
      return false
    }
    throw error
  }
}

export function writeProcessRecord(recordPath, processIdentity) {
  const temporaryPath = `${recordPath}.${process.pid}.tmp`
  writeFileSync(temporaryPath, JSON.stringify(processIdentity))
  renameSync(temporaryPath, recordPath)
}

export function processIdentityIsCurrent(identity) {

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Inspect AggregateError.errors — the first element is the root cause, the rest are recovery failures
  2. After this throw, fall back to killProcessMatchingCommand(environmentFragment) to sweep stray helpers
  3. Ensure compensateStoppedGroup treats EPERM on a uid-changed process as non-fatal if the trial is already being abandoned
  4. Reproduce with the operations parameter injected to log every signal and its target identity

Example fix

// before
try {
  signalValidatedProcessGroup(pgid, frag, sig, state)
} catch (error) {
  console.error(error.message)
}

// after
try {
  signalValidatedProcessGroup(pgid, frag, sig, state)
} catch (error) {
  const causes = error instanceof AggregateError ? error.errors : [error]
  for (const cause of causes) console.error(cause)
  killProcessMatchingCommand([frag])
}
Defensive patterns

Strategy: try-catch

Type guard

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

Try / catch

try {
  signalValidatedProcessGroup(pgid, frag, sig, state)
} catch (error) {
  const causes = error instanceof AggregateError ? error.errors : [error]
  // causes[0] is the original; causes.slice(1) are recovery failures
  for (const c of causes) log.error(c)
  killProcessMatchingCommand([frag])
}

Prevention

When it happens

Trigger: Any throw from the anchor-stop or group-signal path (141, 142, or a raw signal error) AND compensateStoppedGroup records errors — i.e. SIGCONT of -pgid or the anchor throws something other than ESRCH.

Common situations: The owner-loss scenario again: by recovery time the helper's uid has changed, so both the original signal and the resume raise EPERM. Also triggered by tests that inject a signalProcess stub failing on every call.

Related errors


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