stablyai/orca · error

Session "${opts.sessionId}" is terminating

Error message

Session "${opts.sessionId}" is terminating

What it means

A distinct, more specific error than SessionNotFoundError: the session exists, is alive, and is marked isTerminating (e.g., it was SIGKILLed but the child has not yet been reaped). Recreating it now could hide two live generations behind the same public session id. Throwing a unique message lets callers distinguish 'in a terminal exit race' from 'genuinely gone'.

Source

Thrown at src/main/daemon/terminal-host-session-create.ts:60

  if (existing && existing.isAlive && !existing.isTerminating) {
    const snapshot = existing.getSnapshot()
    existing.detachAllClients()
    const token = existing.attachClient(opts.streamClient)
    return {
      isNew: false,
      snapshot,
      pid: existing.pid,
      shellState: existing.shellState,
      incarnationId: existing.incarnationId,
      ...getDaemonSessionResultMetadata(existing),
      attachToken: token
    }
  }

  if (existing?.isAlive && existing.isTerminating) {
    // Why: replacing a SIGKILLed-but-unreaped child could hide two live
    // generations behind the same public session id.
    throw new Error(`Session "${opts.sessionId}" is terminating`)
  }
  if (opts.attachOnly) {
    // Why: an adopted claim proves only one owner generation; it must never
    // turn an exit race into permission to spawn an unclaimed shell.
    throw new SessionNotFoundError(opts.sessionId)
  }

  if (existing) {
    existing.dispose()
    deps.sessions.delete(opts.sessionId)
    deps.onDeadSessionRemoved(opts.sessionId)
  }

  deps.killedTombstones.clearForCreate(opts.sessionId)
  const size = normalizePtySize(opts.cols, opts.rows)
  const wslDistro = resolveWslSessionContext(opts)?.distro
  const subprocess = deps.spawnSubprocess({
    sessionId: opts.sessionId,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Wait for the session's exit to be fully reaped (the exit handler runs) before recreating under the same id.
  2. If you must proceed immediately, create under a new sessionId rather than reusing the terminating one.
  3. Track isTerminating state client-side and avoid reusing ids that are mid-exit.
  4. Ensure the kill->reap->recreate sequence is serialized, not concurrent.
Defensive patterns

Strategy: retry

Type guard

function isSessionTerminating(e: unknown, sessionId: string): boolean {
  return e instanceof Error && e.message === `Session "${sessionId}" is terminating`
}

Try / catch

try {
  return await createOrAttachTerminalSession(opts, deps)
} catch (e) {
  if (e instanceof Error && e.message === `Session "${opts.sessionId}" is terminating`) {
    await waitForSessionReaped(opts.sessionId)  // exit handler runs
    return await createOrAttachTerminalSession(opts, deps)
  }
  throw e
}

Prevention

When it happens

Trigger: createOrAttachTerminalSession where existing?.isAlive && existing.isTerminating, reached after the live-attach branch and not via the sessionTeardown map. The process was killed but reaping/descendant-capture is incomplete.

Common situations: Recreating a session immediately after sending SIGKILL but before the exit handler reaped the process; a createOrAttach racing the reap window of a killed shell; rapid kill+reopen sequences.

Related errors


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