Skip to content

Don't emit terminalSequence outside Claude Code - #89

Open
jjwjr94 wants to merge 1 commit into
warpdotdev:mainfrom
jjwjr94:fix/no-terminal-sequence-outside-claude-code
Open

jjwjr94 wants to merge 1 commit into
warpdotdev:mainfrom
jjwjr94:fix/no-terminal-sequence-outside-claude-code

Conversation

@jjwjr94

@jjwjr94 jjwjr94 commented Sep 22, 2026

Copy link
Copy Markdown

The problem

terminalSequence is a Claude Code-specific hook output field (added in CC 2.1.141). Other harnesses run Claude Code-format hooks but reject unknown keys in hook stdout — so emitting it there produces a visible error instead of a notification.

Codex is the concrete case. It maps hooks/hooks.json onto its own event names and registers all five of this plugin's hooks:

$ codex app-server  # hooks/list
registered hooks: 5
  warp@claude-code-warp:hooks/hooks.json:post_tool_use:0:0      -> on-post-tool-use.sh
  warp@claude-code-warp:hooks/hooks.json:user_prompt_submit:0:0 -> on-prompt-submit.sh
  warp@claude-code-warp:hooks/hooks.json:session_start:0:0      -> on-session-start.sh
  warp@claude-code-warp:hooks/hooks.json:stop:0:0               -> on-stop.sh
  warp@claude-code-warp:hooks/hooks.json:permission_request:0:0 -> on-permission-request.sh

Codex runs hooks with no controlling tty, so emit_terminal_sequence() takes its "unknown CC version" branch, the /dev/tty write fails, and it prints the JSON. Codex's hook output schema has no terminalSequence and denies unknown fields, so the user gets:

• Hook failed
  └ hook returned invalid post-tool-use JSON output
• Hook failed
  └ hook returned invalid user prompt submit JSON output

...on every tool call and every prompt submission. Reproduced directly against all seven scripts — each one emits the field when no tty is available.

The fix

The JSON fallback only fires when CLAUDE_CODE_VERSION is unset. Claude Code always sets CLAUDE_CODE_VERSION (and CLAUDECODE), so inside Claude Code that branch is unreachable; outside it the field is guaranteed invalid. Gating it on a Claude Code env marker means the guess is only made where it can pay off.

No Codex-specific detection — this fixes any current or future harness that borrows the Claude Code hook format.

Verification

All seven hook scripts, no tty, WARP_CLI_AGENT_PROTOCOL_VERSION + WARP_CLIENT_VERSION set:

Scenario Before After
No CLAUDECODE, no CLAUDE_CODE_VERSION (Codex) emits invalid JSON → error silent ✅
CLAUDE_CODE_VERSION=2.1.278 (normal CC path) terminalSequence JSON unchanged ✅
CLAUDECODE=1, version unset (the existing hedge) terminalSequence JSON unchanged ✅
CLAUDE_CODE_VERSION=2.0.0 (old CC) writes to /dev/tty unchanged ✅

bash -n clean. Plugin version bumped 2.2.0 → 2.2.1 in both plugin.json and marketplace.json, matching the convention in #73.

Note on a related gap

Warp notifications from Codex don't currently work at all — the OSC sequence has no route to the terminal there, since Codex hooks have no tty and no output field for one. This PR only stops the errors. Delivering notifications under Codex would need a separate mechanism, happy to file that as an issue if useful.

🤖 Generated with Claude Code

`terminalSequence` is a Claude Code-specific hook output field (added in
2.1.141). Other harnesses run Claude Code-format hooks but reject unknown
keys in hook stdout, so emitting it there turns every hook into a visible
error rather than a notification.

Codex is the concrete case: it maps hooks/hooks.json onto its own event
names and registers all five of this plugin's hooks, but its hook output
schema has no terminalSequence and denies unknown fields. Hooks run with
no controlling tty, so emit_terminal_sequence() falls through to the
JSON branch and the user gets

    hook returned invalid post-tool-use JSON output
    hook returned invalid user prompt submit JSON output

on every single tool call and prompt submission.

That JSON fallback fires only when CLAUDE_CODE_VERSION is unset. Claude
Code always sets CLAUDE_CODE_VERSION (and CLAUDECODE), so inside Claude
Code the fallback is unreachable; outside it the field is guaranteed
invalid. Gate it on a Claude Code env marker so the guess is only made
where it can pay off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant