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

worker_notify_failed

Error message

worker_notify_failed

What it means

Thrown after the post-claim worker notification loop when the final dispatch outcome is not ok. The task was successfully claimed but the worker (e.g. its tmux pane/agent session) could not be notified to start work; the surrounding catch then releases the claim.

Source

Thrown at src/team/runtime.ts:4787

      if (outcome.ok) break;
      if (attempt < maxAssignRetries && config.worker_launch_mode === 'interactive' && config.tmux_session) {
        if (dismissTrustPromptIfPresent(config.tmux_session, workerInfo.index, workerInfo.pane_id, workerInfo.pid, config.tmux_pane_owner_id ?? undefined, config.hud_pane_id ?? undefined)) {
          waitForWorkerReady(
            config.tmux_session,
            workerInfo.index,
            resolveWorkerReadyTimeoutMs(process.env),
            workerInfo.pane_id,
            workerInfo.pid,
            config.tmux_pane_owner_id ?? undefined,
            config.hud_pane_id ?? undefined,
          );
        } else {
          await new Promise<void>(r => setTimeout(r, assignRetryDelayS * 1000));
        }
      }
    }
    if (!outcome.ok) {
      throw new Error('worker_notify_failed');
    }
  } catch (error) {
    // Roll back claim to avoid stuck in_progress tasks on any post-claim dispatch failure.
    const released = await releaseTaskClaim(sanitized, taskId, claim.claimToken, workerName, cwd);

    const reason = error instanceof Error && error.message.trim() !== ''
      ? error.message
      : 'worker_assignment_failed';

    try {
      await writeWorkerInbox(
        sanitized,
        workerName,
        `# Assignment Cancelled\n\nTask ${taskId} was not dispatched due to ${reason}.\nDo not execute this task from prior inbox content.`,
        cwd,
      );
    } catch (err) {
      process.stderr.write(`[team/runtime] operation failed: ${err}\n`);

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Verify the worker pane/process is alive (isWorkerAlive / tmux list-panes) before assigning
  2. If the pane is dead, restart or rescale the worker, then re-assign (the claim is auto-released)
  3. Accept pending trust prompts for the worker session so dispatch can succeed

Example fix

// before
await assignTask(team, taskId, worker, cwd);

// after
if (!isWorkerAlive(config.tmux_session, w.index, w.pane_id, w.pid)) {
  await rescaleWorker(team, worker); // recreate dead pane
}
await assignTask(team, taskId, worker, cwd);
Defensive patterns

Strategy: retry

Validate before calling

const alive = isWorkerAlive(config.tmux_session, w.index, w.pane_id, w.pid, config.tmux_pane_owner_id, config.hud_pane_id);
if (!alive) throw new Error('worker pane dead; restart before dispatch');

Type guard

function workerReachable(cfg: TeamConfig, w: WorkerInfo): boolean {
  return cfg.worker_launch_mode === 'prompt' ? isPromptWorkerAlive(cfg, w) : isWorkerAlive(cfg.tmux_session, w.index, w.pane_id, w.pid);
}

Try / catch

try { await assignTask(t, id, w, cwd); } catch (e) { if ((e as Error).message === 'worker_notify_failed') { await restartWorker(t, w); await assignTask(t, id, w, cwd); } else throw e; }

Prevention

When it happens

Trigger: claimTask succeeds but every dispatch retry (up to 2, including retry delays for trust prompts) fails — e.g. worker pane is dead, tmux send-keys fails, or the agent session is unresponsive.

Common situations: Worker pane crashed or was killed between team start and assignment; tmux session/pane IDs stale after a server restart; trust prompt not accepted so dispatch retries exhaust.

Related errors


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