Add option to disable OSC 8 hyperlink emission (FORCE_HYPERLINK ignored) — ctrl-click opens duplicate tabs in Konsole

Status Fixed / completed
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 1, 2026 · closed Aug 20, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

What happened

Since the newer TUI began emitting OSC 8 hyperlink escape sequences for URLs, ctrl-clicking a link in KDE Konsole opens two browser tabs for a single click.

Root cause (verified)

Konsole has its own regex-based URL detector (Settings → Edit Current Profile → Mouse → "Underline links") in addition to honoring OSC 8 hyperlinks. When Claude Code wraps a URL in an OSC 8 sequence whose visible text is also a valid URL, a single ctrl-click fires both Konsole handlers — the OSC 8 hyperlink and the regex match — so two tabs open.

Verified by isolating the handlers:

  • Uncheck Konsole's "Underline links" → single tab (but the underline is lost). This proves the regex filter is the second handler.
  • Re-check "Underline links" → two tabs again.

FORCE_HYPERLINK=0 does not disable emission

The supports-hyperlinks convention suggests FORCE_HYPERLINK=0 should disable OSC 8 emission, but it has no effect in Claude Code:

  • Set export FORCE_HYPERLINK=0 in the shell, launched claude from that shell.
  • Confirmed inside the running session that the variable is inherited (echo $FORCE_HYPERLINK → 0).
  • Links still open two tabs — Claude Code still emits OSC 8.

So there is currently no way to disable OSC 8 hyperlink emission, and no combination of terminal + Claude Code settings yields both an underline (from the terminal's own filter) and a single-tab open.

Request

Please honor FORCE_HYPERLINK=0 (and/or add a settings.json option or CLI flag) to disable OSC 8 hyperlink emission and fall back to plain-text URLs. That lets terminals with their own URL detection (Konsole, and others with regex link filters) handle links as they did before the OSC 8 change.

Environment

  • Claude Code: newer TUI (OSC 8 hyperlinks enabled)
  • Terminal: KDE Konsole on Linux (Fedora), TERM=xterm
  • This is not the JetBrains duplicate-mouse-event issue (#68568) — it reproduces in Konsole, a standard terminal.

View original on GitHub ↗

3 Comments

liuclare · 2 months ago

Still reproduces on Claude Code 2.1.231 / Konsole 25.12.3. Process-level tracing places the second open elsewhere than the original diagnosis: it is spawned by Claude Code, not by Konsole.

Environment

  • Claude Code 2.1.231, native Linux binary
  • KDE Konsole 25.12.3 (KONSOLE_VERSION=251203), Wayland session, TERM=xterm-256color
  • No custom Konsole profile (~/.local/share/konsole/ has no .profile), so stock defaults including "Underline links"
  • Default browser google-chrome.desktop, single handler in ~/.config/mimeapps.list

One ctrl+click produces two independent process trees

Sampling /proc while ctrl+clicking a single link:

PID=2545570  /bin/bash /usr/bin/google-chrome-stable https://example.com/probe-WATCH2
  chain: systemd(2029) < systemd(1)

PID=2545862  /bin/sh /usr/bin/xdg-open https://example.com/probe-WATCH2
  chain: claude(2490792) < zsh(2311370) < konsole(3471815)
PID=2545866  kde-open https://example.com/probe-WATCH2
  chain: xdg-open(2545862) < claude(2490792)

The first tree is Konsole's link activation through KIO, which on Plasma launches the browser in a systemd user scope. Its parent is systemd --user, not claude. The second is xdg-open spawned by the claude process, then kde-open.

PID ordering (2545570 < 2545862) shows the systemd-launched Chrome was created before Claude Code's xdg-open existed, so it is not a descendant of that chain.

Isolation tests

Each path on its own opens one tab:

| Test | Bypasses | Result |
|---|---|---|
| google-chrome-stable <url> from a shell | Konsole + Claude Code | 1 tab |
| xdg-open <url> from a shell | Konsole + Claude Code | 1 tab |
| printf 'https://example.com/x\n' in a plain Konsole tab, ctrl+click | Claude Code | 1 tab |
| ctrl+click a link inside Claude Code's TUI | nothing | 2 tabs |

Chrome's history DB records two independent external launches rather than one launch that duplicated a tab: both rows have transition core type 6 (START_PAGE, URL passed on the command line), 174 ms / 251 ms / 649 ms apart across three reproductions.

Plain click opens nothing and shift+click opens nothing. Both handlers are bound to ctrl+click, so they fire together, matching what #82078 reports.

Correction to the original diagnosis

The original post attributes the second tab to Konsole honouring the OSC 8 hyperlink in addition to its own regex filter. That does not hold here.

printf '\e]8;;https://example.com/probe-C\e\\CLICK-C\e]8;;\e\\\n'

This emits an OSC 8 hyperlink whose visible text contains no URL. In Konsole it renders as plain CLICK-C, shows no underline on ctrl+hover, and is not clickable, so no OSC 8 hotspot is created. The probe uses ESC \ as the string terminator; the BEL-terminated form was not tested.

The profile UI shows the same state: Konsole's Mouse tab has an "Allow escape sequences for links" checkbox (AllowEscapedLinks), unchecked by default, which is the setting on this machine. With it off, the only hotspot Konsole contributes is the regex one behind "Underline links".

What the process evidence leaves is the root cause #82078 identified from the binary: Claude Code's internal onHyperlinkClick dispatcher spawns xdg-open for allowlisted schemes while the terminal independently activates the link. Konsole is one more environment for it, alongside gnome-terminal (#82078), Windows Terminal/WSL2 (#81905) and WSL (#73968). #82078 proposes gating internal dispatch on the same terminal-capability check that already gates OSC 8 emission.

Workaround

The original post states there is no user-side workaround. One exists as of 2.1.195:

export CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1

With it set, re-running the process sampler shows only the systemd-scoped Chrome, the claude → xdg-open chain is gone, and one tab opens. Posted by @CluelessArchitect in #68568 on 2026-08-07, who notes it takes effect on restart.

It disables click, drag and hover in fullscreen mode, so drag-selection inside the TUI stops working and shift+drag falls back to the terminal's native selection. @joshp-aferry reports the same trade-off in #68568.

FORCE_HYPERLINK=0 was not tested here.

---

Diagnosis performed by Claude Code on the affected machine (process sampling, binary inspection, Chrome history query), reviewed and posted by the account owner.

bcherny collaborator · 1 month ago

Thanks for the detailed report and the Konsole handler isolation — that was really helpful.

I tried to reproduce this on the current release 2.1.233 (Linux, TERM=xterm, Konsole-style environment), capturing the raw terminal byte stream:

  • By default, Claude Code does emit OSC 8 hyperlinks for some links (e.g. sign-in URLs) even when it can't detect terminal support.
  • With FORCE_HYPERLINK=0 set in the shell before launching claude, zero OSC 8 sequences were emitted — links render as plain text, so only Konsole's own regex link handler fires and ctrl-click opens a single tab.
  • With FORCE_HYPERLINK=1, OSC 8 emission returns.

So FORCE_HYPERLINK=0 is now honored as a full off switch for OSC 8 emission — this was fixed in a recent release (changelog), and the variable is documented at https://code.claude.com/docs/en/env-vars (note: it reads a number, so only 0 disables).

Closing as fixed — if you still see OSC 8 emitted with FORCE_HYPERLINK=0 on 2.1.233 or later, please reply with your version and we'll reopen.

🤖 Generated with Claude Code

liuclare · 1 month ago

Re-tested on 2.1.251. FORCE_HYPERLINK=0 does remove OSC 8 emission, but not the duplicate open: with it set and Konsole's "Underline links" on, one ctrl+click still produces two process trees, one from Konsole through KIO and one from claude calling xdg-open. The in-app dispatch is independent of emission, so turning emission off does not turn it off.

This matches the mechanism confirmed in #76022 on 2026-08-17, which is open and marked reproduced. Full isolation runs are posted there. The workaround that works here is /tui default.

---

🤖 Generated with Claude Code