Hooks: unrecognized keys (e.g. `args`) are dropped silently, and hook stdout reaches the terminal with control sequences intact

Status Open
Reported on v2.1.229
Maintainer reply None cached
Activity 3 comments · opened Aug 21, 2026

Summary

Two related robustness gaps, found while diagnosing a third-party integration that had been corrupting the terminal for two days without producing a single error anywhere.

Environment

  • Claude Code 2.1.229 (winget install, installMethod: "native")
  • Windows 11 Pro 26200
  • VS Code integrated terminal

1. Unrecognized keys in a hook entry are dropped silently

A third-party tool wrote hook entries into ~/.claude/settings.json shaped like:

{
  "type": "command",
  "command": "C:\WINDOWS\System32\conhost.exe",
  "args": ["--headless", "C:\WINDOWS\System32\cmd.exe", "/d", "/c", "%USERPROFILE%\...\hook.cmd"],
  "timeout": 10
}

args is not part of the hook schema, so only command ran. The result was that a completely different program executed than the one the config describes — conhost.exe on its own instead of conhost --headless cmd /d /c hook.cmd — and nothing warned about it at any point: not at startup, not on hook dispatch, not in the transcript.

The hook exited 0 every time, so it was recorded as a successful run. From every observable signal, the configuration looked healthy.

Request: validate hook entries and warn on unrecognized keys (or surface it in /doctor). A single "unknown key args in hook entry — ignored" line would have made this a two-minute fix instead of a multi-day mystery. Supporting command + args would also work, but a warning is the cheaper and more general fix.

2. Hook stdout reaches the terminal with control sequences intact

The bare conhost.exe above emitted:

ESC[?9001h ESC[?1004h ESC[?25l ESC[?9001l ESC[?1004l ESC[2J ESC[m ESC[H ESC]0;C:\WINDOWS\System32\conhost.exe BEL ESC[?25h

Consequences, on every tool call (the matchers were "*"):

  • ESC[2J + ESC[H cleared the screen and homed the cursor, visibly wiping terminal output mid-session
  • ESC[?9001h / ESC[?9001l toggled win32-input-mode on and off
  • ESC]0;… retitled the terminal tab

The same bytes were also injected into the conversation transcript, where they appear at the top of every SessionStart and UserPromptSubmit attachment.

Hook stdout is untrusted subprocess output. Stripping or neutralizing ANSI/OSC control sequences before it reaches the terminal — and before it is injected into the transcript — would contain this whole class of failure, misconfigured and hostile hooks alike. A hook should not be able to clear the user's screen or retitle their window as a side effect of its output.

Why it was hard to diagnose

Nothing surfaced the problem. hook_success attachments recorded normal durations (median ~40 ms). The only symptom was visual corruption, and only in terminals that honor those sequences — the same session showed nothing wrong in JetBrains' terminal, which made it look like a VS Code rendering bug rather than a hook problem.

Diagnosis required decoding the escape bytes by hand and correlating transcript timestamps against config-file mtimes to find when the hooks first appeared.

View original on GitHub ↗

3 Comments

maytayci · 9 days ago

Minimal repro (no third-party tool required)

Issue 1 — silent unknown-key drop:

  1. Add to ~/.claude/settings.json:
"hooks": {
  "PreToolUse": [{ "matcher": "*", "hooks": [{
    "type": "command",
    "command": "C:\WINDOWS\System32\conhost.exe",
    "args": ["--headless", "C:\WINDOWS\System32\cmd.exe", "/d", "/c", "echo hook-ran"],
    "timeout": 10
  }]}]
}
  1. Start a session and let any tool run.
  2. Observed: no warning about args at startup, on dispatch, or in /doctor; the bare conhost.exe executes instead of the intended command; the run is recorded as hook_success with normal duration (median ~40 ms here).
  3. Expected: something like unknown key "args" in hook entry — ignored, anywhere.

Issue 2 — unsanitized hook stdout: the bare conhost.exe above already demonstrates it on Windows (screen cleared on every tool call). A cross-platform one-liner shows the same class: set a PreToolUse hook command to printf '\033[2J\033[H' — the user's screen is wiped on every tool call, and the raw sequences are also injected into the transcript's hook attachments.

Real-world occurrence of exactly this shape: stablyai/orca#15797 (a third-party integrator wrote command+args entries in good faith; the schema mismatch went undetected for days).

maytayci · 9 days ago

Update: the third-party trigger for this (stablyai/orca#15797) is now fixed upstream — Orca 1.4.187 writes a single self-contained command string per hook instead of command+args, so the bare-conhost.exe-clearing-the-screen symptom is gone on current Orca. Verified on my machine after updating 1.4.185 → 1.4.187 and relaunching once.

That said, the two robustness gaps requested here are independent of Orca and still stand:

  1. Unrecognized keys in a hook entry (e.g. args) are still dropped silently — no warning at startup, on dispatch, or in /doctor. Any other tool that writes a hook entry with an extra/misnamed key would hit the same silent-failure mode Orca did.
  2. Hook stdout still reaches the terminal (and the transcript's hook attachments) with ANSI/OSC control sequences intact, so a misconfigured or hostile hook can still clear the screen / retitle the tab / etc.

Leaving this open for those two asks rather than closing it as "fixed by Orca" — the underlying gap in Claude Code is what let a schema mismatch run silently for days regardless of which tool caused it.

amomurawskiio425-cloud · 7 days ago

Great diagnosis — and the "exits 0, no error anywhere" is the cruel part: the config looks healthy from every signal while the terminal is being corrupted. Your #1 is the actual mechanism, and I can tell you the "third-party integration" that wrote it: that exact command: conhost.exe + args:[--headless, cmd.exe, /d, /c, …] shape is Orca (stablyai/orca), which injects status hooks into Claude Code, Codex, Cursor, Grok, etc. Claude Code runs only command, so it launches bare conhost.exe — and then that emits the ANSI sequences you saw.

Two extra facts that'll save you time: Orca writes that shape for all 11 hook events, and it re-installs the entries on every launch. So hand-deleting them from ~/.claude/settings.json is a treadmill — they come straight back.

What ended it for me was cleaning the Orca-written entries (only Orca's rows, leaving my own hooks alone, and skipping while Orca is running so it can't just rewrite them). The bare-conhost + runaway control-sequences stopped at the source for me.

Honest caveat: it won't restore any config Orca already mangled beyond the hooks, and it's a no-op while Orca is active. But the culprit is Orca, and removing its entries is the fix. https://github.com/edison7009/Coffee-CLI/releases/tag/v3.3.8