Skip to content

Reject unknown terminal modes instead of spawning dead terminals#398

Merged
danshapiro merged 1 commit into
mainfrom
fix/mcp-invalid-terminal-mode
Jun 8, 2026
Merged

Reject unknown terminal modes instead of spawning dead terminals#398
danshapiro merged 1 commit into
mainfrom
fix/mcp-invalid-terminal-mode

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

Problem

new-tab / split-pane via the MCP (and the REST API generally) accepted any mode string and passed it straight to the spawn path with no validation. For a mode that is neither the built-in 'shell' nor a registered coding CLI (claude/codex/opencode/gemini/kimi), buildSpawnSpec fell back to exec-ing the mode name itself:

const cli = resolveCodingCliCommand(mode, ...)  // null for an unknown mode
const cmd = cli?.command || mode                 // → "terminal"
return { file: cmd, ... }                         // pty.spawn("terminal") → execvp fail

So e.g. new-tab({ mode: "terminal" }) spawned a process literally named terminal that died instantly with execvp(3) failed: No such file or directory, leaving a tab whose terminal was already gone. The WebSocket terminal.create path already validates modes; the REST/MCP path did not.

Fix

Validate at the authoritative spawn point. buildSpawnSpec now throws UnknownTerminalModeError for any non-shell mode that isn't a registered coding CLI, with a message listing the valid modes. The REST router maps that error to a 400, so the MCP surfaces a clear message instead of a zombie terminal. Because the guard sits in buildSpawnSpec, it covers every caller (MCP, REST, CLI) on all platforms — including the WSL branch, which previously silently fell back to bash for unknown modes.

Adds getKnownTerminalModes() / isKnownTerminalMode() helpers.

Tests

  • buildSpawnSpec throws UnknownTerminalModeError for an unknown mode instead of returning a spec that would exec the mode name.
  • POST /api/tabs with an unknown mode returns 400 and rolls back the half-created tab.

357 tests pass across the two affected files; server typecheck is clean.

Note

Source-only fix. The running server (dist/) needs a npm run build:server + restart for the live MCP to reject invalid modes.

🤖 Generated with Claude Code

new-tab/split-pane via the MCP (and the REST API generally) accepted any
`mode` string and passed it straight to the spawn path. For a mode that is
neither the built-in 'shell' nor a registered coding CLI, buildSpawnSpec fell
back to exec-ing the mode name itself (`cli?.command || mode`), so e.g.
`mode: "terminal"` spawned a process literally named "terminal" that died
instantly with "execvp(3) failed: No such file or directory" -- leaving a tab
whose terminal was already gone.

Validate the mode at the authoritative spawn point: buildSpawnSpec now throws
UnknownTerminalModeError for any non-shell mode that isn't a registered coding
CLI, listing the valid modes. The REST router maps that error to a 400, so the
MCP surfaces a clear message instead of a zombie terminal. This mirrors the
validation the WebSocket terminal.create path already performs.

Adds getKnownTerminalModes/isKnownTerminalMode helpers and regression tests at
both the buildSpawnSpec and REST-route layers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danshapiro danshapiro merged commit 7518ada into main Jun 8, 2026
1 check passed
@danshapiro danshapiro deleted the fix/mcp-invalid-terminal-mode branch June 8, 2026 05:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants