Yeachan-Heo/oh-my-codex · warning · Error

shutdown_rejected:${detail}

Error message

shutdown_rejected:${detail}

What it means

During the worker-ack phase of shutdown (when skipWorkerAcks is false), one or more workers explicitly rejected the shutdown and force was not set. The message lists each rejecting worker and its reason as 'worker:reason' pairs.

Source

Thrown at src/team/runtime.ts:5064

      for (const w of config.workers) {
        const ack = await readShutdownAck(sanitized, w.name, cwd, shutdownRequestTimes.get(w.name));
        if (ack && !ackedWorkers.has(w.name)) {
          ackedWorkers.add(w.name);
          await appendTeamEvent(sanitized, {
            type: 'shutdown_ack',
            worker: w.name,
            reason: ack.status === 'reject' ? `reject:${ack.reason || 'no_reason'}` : 'accept',
          }, cwd);
        }
        if (ack?.status === 'reject') {
          if (!rejected.some((r) => r.worker === w.name)) {
            rejected.push({ worker: w.name, reason: ack.reason || 'no_reason' });
          }
        }
      }
      if (rejected.length > 0 && !force) {
        const detail = rejected.map(r => `${r.worker}:${r.reason}`).join(',');
        throw new Error(`shutdown_rejected:${detail}`);
      }

      const anyAlive = config!.workers.some((w) => (
        config!.worker_launch_mode === 'prompt'
          ? isPromptWorkerAlive(config!, w)
          : isWorkerAlive(sessionName, w.index, w.pane_id, w.pid, config!.tmux_pane_owner_id ?? undefined, config!.hud_pane_id ?? undefined)
      ));
      if (!anyAlive) break;
      // Sleep 2s
      await new Promise(resolve => setTimeout(resolve, 2000));
    }

    const anyAliveAfterWait = config!.workers.some((w) => (
      config!.worker_launch_mode === 'prompt'
        ? isPromptWorkerAlive(config!, w)
        : isWorkerAlive(sessionName, w.index, w.pane_id, w.pid, config!.tmux_pane_owner_id ?? undefined, config!.hud_pane_id ?? undefined)
    ));
    if (anyAliveAfterWait && !force) {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Inspect the per-worker reasons in the message and address them (finish tasks, answer prompts, restart a wedged worker)
  2. Re-run shutdown after the workers stop rejecting; use force to override deliberate holds
  3. If a single worker is wedged, restart it and retry the graceful shutdown

Example fix

# before
omx team shutdown myteam
# error: shutdown_rejected:worker-2:task_in_progress

# after
omx team task wait myteam --all
omx team shutdown myteam  # or: omx team shutdown myteam --force
Defensive patterns

Strategy: retry

Validate before calling

const acks = await pollWorkerAcks(teamName, cwd);
if (acks.some(a => !a.ok)) throw new Error('workers will reject; drain work first');

Try / catch

try { await shutdownTeam(t, cwd, {}); } catch (e) { const m = /^shutdown_rejected:(.+)$/.exec((e as Error).message); if (m) { await resolveRejectingWorkers(t, m[1]); await shutdownTeam(t, cwd, {}); } else throw e; }

Prevention

When it happens

Trigger: Calling shutdown without force while at least one alive worker returns a rejection ack (a non-ok ack.reason) during the acknowledgement round.

Common situations: A worker agent mid-task rejects shutdown to protect in-flight work; worker prompts require interactive confirmation; one unresponsive/misbehaving worker returns a rejection reason.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/7941d597b2265540. Report an issue: GitHub.