[Bug] Mouse clicks not responding in TUI on Terminal.app v2.1.193

Status Open
Reported on v2.1.193
Maintainer reply None cached
Activity 6 comments · opened Jun 25, 2026

Bug Description
Mouse clicks stopped registering in Terminal.app (v2.1.193)

Clicking no longer works in the TUI. Clicking a link does nothing, and in agents mode clicking an agent no longer enters that agent. This regressed recently, it used to work.

Mouse-wheel scrolling inside Claude Code still works fine, so mouse events are reaching the app. It's specifically the click targets that don't respond.

Environment:

  • Claude Code v2.1.193
  • macOS, Terminal.app (native), zsh
  • No tmux or screen
  • CLAUDE_CODE_DISABLE_MOUSE not set
  • No mouse/vim/terminal settings in settings.json

Restarting Claude Code doesn't fix it. Happy to test in iTerm2 to confirm whether it's Terminal.app specific if that helps.

Environment Info

  • Platform: darwin
  • Terminal: xterm-256color
  • Version: 2.1.193
  • Feedback ID: 9004ff71-d07b-4425-aa64-ad95f05b6bbf

Errors

[]

View original on GitHub ↗

6 Comments

github-actions[bot] · 2 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/66808
  2. https://github.com/anthropics/claude-code/issues/68509
  3. https://github.com/anthropics/claude-code/issues/70894

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

TwoToneBytes · 2 months ago

Adding detail to help tell this apart from the mouse-capture issues:

Mouse-wheel scroll inside Claude Code still works, so mouse events are reaching the app. It's specifically the in-app click targets that stopped responding. Clicking a link does nothing, and in agents mode clicking an agent no longer enters it.

That's the inverse of #61936 / #70539, which are about mouse capture breaking terminal-native actions. Here the in-app clicks themselves are dead while scroll is fine.

Environment:

  • Claude Code v2.1.193
  • macOS, Terminal.app (native), zsh
  • No tmux or screen
  • CLAUDE_CODE_DISABLE_MOUSE not set
  • No mouse/vim/terminal settings in settings.json

Could this be a regression from the v2.1.150+ mouse-tracking changes that only affects in-app click handling on Terminal.app? Possibly related to #61936. Happy to test in iTerm2/Ghostty to confirm whether it's Terminal.app specific.

TwoToneBytes · 2 months ago

Not a duplicate of the three flagged above. Each is a different (often inverse) symptom:

  • #68509: clicking turns the viewport white. That's a rendering bug, not clicks failing to register.
  • #66808: mouse-wheel scroll and copy/paste break over SSH in 2.1.170. Inverse of this one. There scrolling breaks; here scrolling works fine and only clicks are dead.
  • #70894: clicking accidentally triggers a selection/enter in pickers. Also inverse. There clicks fire too eagerly; here they don't register at all.

This issue is specifically: mouse-wheel scroll works (so mouse events are reaching the app), but in-app click targets don't respond. Clicking a link does nothing, and clicking an agent in agents mode no longer enters it. macOS Terminal.app, v2.1.193.

TwoToneBytes · 2 months ago

Dug into the 2.1.193 native binary (minified bundle) to find why scroll works but clicks don't in the agents/FleetView view. Symbol names below are the minified ones from this build; maintainers can map them to source.

Root cause: click and hyperlink dispatch are gated on altScreenActive, wheel dispatch is not.

The renderer has three methods. Wheel runs whenever mouse reporting is on; click and hyperlink both bail early if altScreenActive is false:

dispatchWheelEvent = (e) => { /* hit-tests + dispatches, NO altScreen gate */ }
dispatchClick(e,t){ if(!this.altScreenActive) return false; /* MNi() hit-test → node.onClick */ }
getHyperlinkAt(e,t){ if(!this.altScreenActive) return; /* ... */ }

So when altScreenActive === false: wheel still works, a left-click on an agent row returns false immediately (row never entered), and the hyperlink lookup returns undefined so the open-link path can never fire. That matches the report exactly (scroll works, row-click and link-click both dead).

The input path confirms the split. The SGR parser drops wheel before it ever becomes a "mouse" event (if((n & 64) !== 0) return null), so wheel is routed as a wheelup/wheeldown key to dispatchWheelEvent, while press/release become kind:"mouse" and go through the gated dispatchClick / getHyperlinkAt (via the mouse handler that calls onClickAt then falls back to getHyperlinkAt + onOpenHyperlink).

Why altScreenActive is false in the agents view. Its only setter is the mouse-tracking mount effect, and the enable-write is decoupled from the flag-set by an optional chain:

i(ake() + (o ? ike : "")),     // writes the mouse-enable escapes (1000h/1006h) → wheel works
d?.setAltScreenActive(true, o) // d = xu.get(process.stdout); if undefined this no-ops → clicks stay dead

If the renderer registered under process.stdout is absent, the mouse escapes still get written (terminal starts reporting, wheel works) but altScreenActive never flips true (click/hyperlink stay gated off). On top of that, the main REPL mounts the mouse-tracking wrapper unconditionally, while FleetView only mounts it when the fullscreen gate MMt() is true, and MMt() does not special-case CLAUDE_CODE_SESSION_KIND==="bg" the way the mouse-on check Urt() does. So mouse reporting is force-on for bg, but the click gate isn't satisfied.

Proven vs not:

  • Proven from the code: the gate asymmetry (wheel ungated, click/hyperlink gated on altScreenActive), and that the flag's sole setter is the mount effect, decoupled from the enable-write via d?..
  • Not proven from strings alone: the exact runtime reason the flag ends up false in a bg session (stdout redirect making xu.get(process.stdout) undefined, vs MMt() returning false so the wrapper never mounts). That needs a live trace under CLAUDE_CODE_SESSION_KIND=bg.

Suggested fix: gate click/hyperlink dispatch on mouse-tracking the same way wheel already is, rather than on full alt-screen, e.g. if(!this.altScreenActive && !this.altScreenMouseTracking) return false; in dispatchClick (and the equivalent in getHyperlinkAt). Alternatively, ensure the bg/FleetView path actually calls setAltScreenActive(true) on the active renderer when it force-enables mouse, or make the fullscreen gate treat bg as fullscreen so the wrapper mounts.

Environment: Claude Code 2.1.193, macOS Terminal.app, no tmux, agents/FleetView (background session).

TwoToneBytes · 1 month ago

Still reproduces on 2.1.216. This time I captured the raw terminal input instead of reasoning from the bundle, which rules out the input layer entirely and isolates the failure to the altScreenActive gate from my earlier comment.

Captured input. I enabled the same modes the fullscreen renderer uses (1000h 1002h 1003h 1006h) in a bare Terminal.app tab and dumped every report:

plain left click   \x1b[<0;13;9M    \x1b[<0;13;9m     button=0   mods=none
ctrl+click         \x1b[<16;30;24M  \x1b[<16;30;24m   button=16  mods=CTRL   <-- passes (button&24)
ctrl+drag          \x1b[<51;33;1M                     button=51  mods=CTRL+MOTION
right click        \x1b[<2;42;53M   \x1b[<2;42;53m    button=2
middle click       \x1b[<1;51;11M   \x1b[<1;51;11m    button=1
wheel up / down    \x1b[<64;94;35M  \x1b[<65;42;53M
motion             \x1b[<35;94;31M                    button=35  mods=MOTION

Terminal.app supports SGR 1006 correctly and delivers press, release, drag, motion and wheel. Ctrl+click arrives with bit 16 set, so it satisfies the (t.button&24)!==0 requirement in the hyperlink path. The input layer is healthy.

Cmd+click never appears in the capture at all. Terminal.app consumes it for its own URL handling and never forwards it to the application. So on Apple Terminal.app, Ctrl+click is the only modifier gesture Claude Code can ever see, and any Cmd+click that appears to open a link is Terminal.app doing it natively off the visible URL text, not Claude Code. Worth noting because the 2.1.181 changelog and the docs present Cmd+click as the macOS gesture, which cannot work here by construction.

Which leaves the gate as the only remaining explanation. Unchanged in 2.1.216:

dispatchClick(e,t){ if(!this.altScreenActive) return!1; ... }
dispatchHover(e,t){ if(!this.altScreenActive) return; ... }
dispatchWheelEvent=(e)=>{ /* no gate */ }

I then confirmed every other precondition is satisfied at once, in a restarted session with FORCE_HYPERLINK=1 (so OSC 8 anchors are actually emitted, which Terminal.app otherwise never gets since it has no OSC 8 support) and tui: "fullscreen":

  • input arrives, with the CTRL bit, per the capture above
  • (t.button&24)!==0 is satisfied by Ctrl+click
  • hyperlink anchors exist, since FORCE_HYPERLINK=1 forces emission
  • the mouse pipeline is live, because wheel scrolling works in that same session

Ctrl+click still does not open a link, and clicking an agent row still does not enter it. Wheel is the only one of the three consumers without the altScreenActive gate, and it is the only one that works. By elimination altScreenActive is false while mouse tracking is on. That is the same conclusion as my 2.1.193 analysis, now with the input layer, the modifier gate, and anchor availability all eliminated as variables.

tui: "fullscreen" is not a workaround. I set it explicitly and retested; clicks stay dead. Worth stating because two fullscreen predicates disagree for background sessions:

function cs(e){   // bg forced true
  if(l2()==="local-agent") return!1;
  if(Z.CLAUDE_CODE_SESSION_KIND==="bg") return!0;
  ...
  switch(zn().tui){case"fullscreen":return!0;case"default":return!1}
}
function WJt(e){  // no bg special-case
  if(aM()) return!1;
  if(eGi()) return!1;
  if(Z.CLAUDE_CODE_NO_FLICKER===!0) return!0;
  if(Zji()) return!1;
  if(UAe(e)) return!1;
  switch(zn().tui){case"fullscreen":return!0;case"default":return!1}
  return!0
}

TERM_PROGRAM is unset in background sessions. I checked the live env of two running bg agents; neither has it, only the parent daemon does. Every terminal-quirk helper reads this.proc.env.TERM_PROGRAM, so in a bg session they all silently evaluate false, including macCmdClickArrivesWithoutSgrModifierBit(). Ghostty and Warp users lose that hatch in background sessions, which probably widens this past Terminal.app.

Workarounds, since the usual advice does not apply to bg sessions. /tui default and CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 (recommended in #61936, #69446, #76157) are both short-circuited:

if(Z.CLAUDE_CODE_SESSION_KIND==="bg") return!0;   // checked before either opt-out

CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1 does not help either, since scroll mode still enables button reporting (_Xg = 1000h + 1006h). Only CLAUDE_CODE_DISABLE_MOUSE=1 reaches "off", which writes no mouse escapes and returns clicks to the terminal so its native Cmd+click works, provided links render as visible text (FORCE_HYPERLINK unset, since Terminal.app has no OSC 8 support).

Suggested fix, same as before: gate click and hyperlink dispatch on mouse tracking rather than alt-screen, e.g. if(!this.altScreenActive && !this.altScreenMouseTracking) return false;, or ensure the bg path calls setAltScreenActive(true) on the active renderer when it force-enables mouse. Separately, the quirk helpers should not silently depend on TERM_PROGRAM in bg sessions.

Unrelated observation: wheel reports are delivered by the terminal but wheel scrolling does not scroll the fullscreen transcript, only PgUp/PgDn works. Looks like a separate issue; I have not dug in.

Proven vs not. Proven: the captured input above including the CTRL bit passing the gate, the absence of any Cmd+click report, the dispatch gate asymmetry, the two disagreeing predicates, the bg short-circuit, the missing TERM_PROGRAM, and the mode-to-escape mapping. Not proven: the exact runtime reason altScreenActive ends up false, which still needs a live trace under CLAUDE_CODE_SESSION_KIND=bg.

Environment: Claude Code 2.1.216, macOS Terminal.app, no tmux, background/agents session. Retested with both tui: "default" and tui: "fullscreen".

TwoToneBytes · 1 month ago

Follow-up with a stronger data point: two background sessions on the same machine, same binary, same terminal and same environment behave differently. That makes this a runtime race rather than a static gate or a config problem.

I have 12 live Claude Code processes on 2.1.216, all background/agents sessions in the same Apple Terminal.app. I dumped the environment of every one of them:

FORCE_HYPERLINK=1                  all 12
CLAUDE_CODE_DISABLE_MOUSE          unset in all 12
CLAUDE_CODE_DISABLE_MOUSE_CLICKS   unset in all 12

Identical. Yet:

  • In one session, right-clicking a URL brings up Terminal.app's own context menu ("Quick Look Link / Open Link / Search with Google"), and links open normally. That can only happen if Claude Code never enabled mouse reporting in that session, leaving the terminal in charge of clicks.
  • In another session opened from the same terminal, that context menu never appears and neither plain click, Ctrl+click nor Cmd+click does anything. Mouse reporting is on there, but as established in my previous comment altScreenActive is false, so dispatchClick and getHyperlinkAt both bail while the ungated dispatchWheelEvent keeps working.

So the same build lands in two different states depending only on how the session was entered. Since config is identical across all of them, the variable has to be the runtime path that mounts the mouse-tracking wrapper, which is the decoupling I described earlier:

i(ake() + (o ? ike : ""))      // writes the mouse-enable escapes
d?.setAltScreenActive(true, o) // no-ops silently if d is undefined

Whether those two land together appears to vary per session. That is consistent with #73443, where footer navigation re-enables mouse capture mid-session and ignores the opt-out from then on.

Practical consequence: the behaviour is not reproducible from configuration alone, which probably explains why this bug and the related mouse-capture reports have been hard to pin down and why the recommended workarounds appear to work for some people and not others. Two users on identical settings can see opposite behaviour.

One more observation worth checking on your side: background sessions inherit their environment from the long-running claude daemon process, whose env is fixed when the daemon starts. On this machine the daemon carries FORCE_HYPERLINK=1, and every session it spawned has it, including ones started after I had removed that variable from settings.json. If that is the general behaviour, env changes in settings.json may not reach background sessions until the daemon is restarted, which would be worth documenting independently of this bug.

Environment: Claude Code 2.1.216, macOS Terminal.app, no tmux, background/agents sessions.