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

sendToWorker: failed to send text: ${send.stderr}

Error message

sendToWorker: failed to send text: ${send.stderr}

What it means

Thrown when the tmux `send-keys -l` command used to deliver literal text to a worker pane exits non-zero. The error wraps tmux's own stderr, so the underlying cause is a tmux-level failure such as a dead/missing target pane, a detached session, or tmux not being installed. This is a process-spawn failure, not a validation error.

Source

Thrown at src/team/tmux-session.ts:3452

  if (strategy !== 'auto') return false;
  if (!paneBusyAtStart) return false;
  if (typeof latestCapture !== 'string') return false;

  const normalizedText = normalizeWorkerTriggerForDraftMatch(text);
  if (normalizedText === '') return false;

  const normalizedCapture = normalizeWorkerTriggerForDraftMatch(latestCapture);
  if (!normalizedCapture.includes(normalizedText)) return false;
  if (paneHasActiveTask(latestCapture)) return false;
  if (!paneLooksReady(latestCapture)) return false;
  return true;
}

async function sendLiteralTextOrThrow(resolveTarget: AsyncPaneTargetResolver, text: string): Promise<void> {
  const target = await requireAsyncPaneTarget(resolveTarget);
  const send = await runTmuxAsync(['send-keys', '-t', target, '-l', '--', text]);
  if (!send.ok) {
    throw new Error(`sendToWorker: failed to send text: ${send.stderr}`);
  }
}

function paneHasQueuedCodexSubmission(captured: string | null | undefined): boolean {
  const normalized = normalizeTmuxCapture(captured ?? '');
  if (normalized === '') return false;
  return /messages to be submitted after next tool call/i.test(normalized)
    || /press esc to interrupt and send immediately/i.test(normalized);
}

async function attemptSubmitRounds(
  resolveTarget: AsyncPaneTargetResolver,
  text: string,
  rounds: number,
  queueFirstRound: boolean,
  submitKeyPressesPerRound: number,
): Promise<boolean> {
  const presses = Math.max(1, Math.floor(submitKeyPressesPerRound));

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Check send.stderr in the message: 'no such pane'/'can't find session' means the pane/session is gone — recreate the worker pane and retry
  2. Verify tmux is installed and the tmux server is running: `tmux ls`
  3. Re-resolve the pane target (list-panes) instead of reusing a cached pane id
  4. If panes are frequently closing, review worker startup/keepalive logic

Example fix

// before
await sendLiteralTextOrThrow(resolveTarget, text);

// after
try {
  await sendLiteralTextOrThrow(resolveTarget, text);
} catch (err) {
  // pane may be gone: re-resolve or respawn worker
  await respawnWorkerPane();
  await sendLiteralTextOrThrow(freshResolveTarget, text);
}
Defensive patterns

Strategy: retry

Validate before calling

const panes = await runTmuxAsync(['list-panes', '-a', '-F', '#{pane_id}']);
if (!panes.ok || !panes.stdout.includes(targetPaneId)) {
  await respawnWorkerPane();
}

Try / catch

try { await sendLiteralTextOrThrow(resolveTarget, text); } catch (err) { if (/failed to send text/.test(String(err))) { await respawnWorkerPane(); await sendLiteralTextOrThrow(freshTarget, text); } else throw err; }

Prevention

When it happens

Trigger: Calling sendLiteralTextOrThrow (or a higher-level sendToWorker path) after the target pane was closed, the tmux server was killed, the session name no longer resolves, or the tmux binary is missing from PATH (runTmuxAsync fails). Also occurs when the resolveTarget pane id becomes stale between resolution and send.

Common situations: Worker pane closed by the agent or user mid-run; tmux server restarted; SSH session dropped taking the tmux server with it; stale pane id cached from a previous run; tmux not installed in CI containers.

Related errors


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