[FEATURE] UserInputChange hook — expose the prompt input buffer to hooks, mirroring MessageDisplay

Status Open
Reported on v2.1.152
Maintainer reply None cached
Activity 0 comments · opened Jul 28, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Claude Code has hooks on nearly every surface: session lifecycle, tool calls, permissions, compaction, worktrees, config changes, task creation, instruction loading, and — since 2.1.152 — assistant message display.

The one surface with no hook is the prompt input buffer.

UserPromptSubmit fires after Enter, when composition has already finished. It can block submission, so pre-send interception is already solved. What it cannot do is modify the prompt — there is no updatedInput equivalent on the prompt path, only additionalContext — or run at all during composition. Nothing fires while the user is typing, which blocks every input-side behavior that has to assist or alter the buffer before it is committed.

The symmetry that's missing. 2.1.152 shipped:

Added a MessageDisplay hook event that lets hooks transform or hide assistant message text as it is displayed

That establishes the principle — hooks may transform text in flight on a display path. There is no counterpart on the input side.

| Direction | Surface | Hook |
|---|---|---|
| Assistant → user | message display | MessageDisplay (2.1.152) |
| User → assistant | prompt input buffer | missing |

Proposed Solution

A UserInputChange hook event, debounced.

"UserInputChange": {
  "command": "~/.claude/hooks/input-watch.sh",
  "debounce_ms": 500
}

Fires after N ms of typing inactivity (default 500). Opt-in — with no config present, nothing fires and behavior is unchanged.

Payload (stdin JSON):

{
  "hook_event_name": "UserInputChange",
  "session_id": "abc123",
  "input": "how do I refcator this functon",
  "cursor_position": 30
}

Handler output (stdout):

{
  "suggestion": "how do I refactor this function",
  "annotation": "Did you mean: refactor, function?"
}

Same stdin/stdout JSON contract as existing hooks. Handlers are user-supplied scripts, so no autocorrect, history, or scanning logic ships in the core TUI — the same division of labor as MessageDisplay.

Staleness. A handler round-trips while the user keeps typing, so a response can arrive computed against a buffer that no longer exists. The input field doubles as a snapshot token: the TUI applies a suggestion only if the buffer is unchanged since that snapshot (compare-and-swap), and surfaces it through the same ghost-text / tab-to-accept affordance the emoji and @ completions already use. A stale, slow, or malformed response is silently dropped. Nothing here needs new suggestion UI.

Alternative Solutions

UserPromptSubmit — fires after Enter, and it can block: exit code 2 or {"decision": "block"} stops the prompt before Claude processes it. Anything that only needs a pre-send veto — including secret scanning — is buildable today and does not need this hook. Two things it cannot do: it cannot modify the prompt (unlike PreToolUse, which has updatedInput, the prompt path has no mutation field — only additionalContext), and it cannot act during composition, so it cannot drive a live token count, autocomplete, or any assist that has to appear before the user commits. The gap this request addresses is mutation and liveness, not interception.

Shipping each feature natively — the alternative is Claude Code building and maintaining a history UI, a token meter, a secret scanner, and a custom autocomplete engine separately. #32517 and #40369 both requested history recall; both were auto-closed unbuilt and are now locked. One hook lets users build these themselves.

External terminal tooling (atuin, text expanders) — these operate on the shell, not on Claude Code's TUI input, so they cannot see or modify the prompt buffer.

Priority

Medium - Would be very helpful

Feature Category

Interactive mode (TUI)

Use Case Example

Concrete scenario — a running token estimate while composing:

  1. I'm near a context limit and start writing a long prompt, pasting in a stack trace and a config file.
  2. A UserInputChange handler re-estimates tokens ~500ms after I stop typing.
  3. It returns {"annotation": "~4.1k tokens"}.
  4. I see the number climbing while there is still something to do about it — I trim the paste before committing.

The same seam covers the rest of the input-side list:

  • Fuzzy recall over past prompts (wiring up fzf/atuin myself), inserted into the buffer
  • Project-specific autocomplete for ticket IDs and symbols from the current file
  • Input normalization and word prediction as an assistive behavior

What this is not asking for. Pre-send interception already exists — UserPromptSubmit can block on exit 2, so a secret scanner that refuses to send is buildable today without any new hook. What has no path is (a) changing the buffer's contents and (b) doing anything at all during composition. Every case above needs one or both.

Additional Context

The machinery already exists. The obvious objection to an input-side hook is cost on the keystroke path. Shipped work suggests that's already solved:

  • 2.1.217 — emoji shortcode autocomplete in the prompt input: type :hea for suggestions, disable with emojiCompletionEnabled. That is read partial buffer → match → offer suggestion → insert, with a settings toggle.
  • /, @, and path autocomplete do the same against different sources.
  • 2.1.219 — per-keystroke input-line echo handling in --ax-screen-reader mode.

The buffer is already read and matched against on every keystroke. This request exposes that existing seam to hook authors rather than adding new machinery. Debounced delivery also means handlers run far less often than the render path already does.

On cost to the input path. The obvious objection is that user code on the keystroke path can hang typing — a slow handler making input feel broken is a worse outcome than the missing feature. Three things in this design are aimed at that: delivery is debounced (default 500ms of inactivity, so handlers run orders of magnitude less often than the render path already does), opt-in (absent config, no process spawns and behavior is byte-identical), and should carry a short timeout whose expiry drops the suggestion rather than stalling the buffer. Debounced delivery also means handlers see in-progress text, the way an editor's live diagnostics do; re-firing on each subsequent pause lets a handler correct itself, and anything that must judge the final prompt as a whole belongs at UserPromptSubmit. Handlers are advisory — the TUI renders the buffer from its own state, and a handler that is slow, crashes, or returns garbage is ignored. MessageDisplay already accepted this class of tradeoff on the display path.

Accessibility angle. Real-time rewriting, word prediction, and input normalization are exactly the assistive behaviors that need input-side access. This would complement the --ax-screen-reader work across 2.1.216–2.1.219, which so far improves output announcement but leaves input assistance to external tools that can't see the buffer.

Related. Supersedes #48294 (same proposal, auto-closed for inactivity before this framing was worked out). Retires the extensibility half of #32517 and #40369, both auto-closed and now locked.

Filed against 2.1.220.

View original on GitHub ↗