[BUG] OSC 8 hyperlinks in custom statusline broken inside tmux — rendering layer analysis

Status Closed — not planned
Reported on v2.1.73
Maintainer reply None cached
Activity 8 comments · opened Mar 21, 2026 · closed May 21, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

OSC 8 hyperlinks from custom statusline programs don't render as clickable links when Claude Code runs inside tmux. This is a re-report of #23438 (closed as stale/locked), now with root cause analysis.

Related issues: #21586, #19976, #14011

Root Cause: Claude Code renders its statusline output outside Ink's React component tree, writing directly to the terminal. Ink's rendering cycle erases and rewrites all managed terminal lines on every update. This destroys any OSC 8 hyperlinks that were written directly to stdout by external statusline programs.

Ink itself is not the problem. As of slice-ansi v8 and @alcalzone/ansi-tokenize v0.3+, Ink's character grid pipeline fully supports OSC 8 round-tripping. Hyperlinks that flow through the Ink React tree survive rendering. The issue is that statusline content bypasses this tree entirely.

Rendering pipeline walkthrough:

  1. External statusline program writes OSC 8 hyperlinks to stdout
  2. Ink's next render cycle calls eraseLines() to clear previous output
  3. Ink rewrites its grid-rendered content (which doesn't include the external hyperlinks)
  4. The hyperlink text appears but is no longer clickable — the OSC 8 sequences were erased

What Should Happen?

OSC 8 escape sequences from statusline programs should pass through Claude Code's rendering layer unchanged and render as clickable hyperlinks inside tmux, just as they do outside tmux.

Suggested fix: Route statusline content through the Ink component tree rather than writing it directly to the terminal:

  1. Render statusline output inside an Ink <Text> node (preserving raw OSC 8 sequences)
  2. Use Ink's <Link> component for statusline hyperlinks
  3. Ensure the statusline rendering region is excluded from Ink's eraseLines() scope

This is a Claude Code change only — no upstream Ink changes are needed.

Error Messages/Logs

No error messages. The statusline text renders visually, but hyperlinks are not clickable. Verified with `xxd` that the statusline binary outputs correct OSC 8 escape sequences — they are simply erased by Ink's render cycle.

Evidence that tmux is not the issue:
- `printf '\033]8;;https://example.com\033\\Click\033]8;;\033\\\n'` in the same tmux pane renders as a clickable link
- Same statusline works when running Claude Code outside tmux

Steps to Reproduce

  1. Configure a custom statusline that outputs OSC 8 hyperlinks:

``
\033]8;;https://example.com\033\\link text\033]8;;\033\\
``

  1. Start tmux with allow-passthrough on and hyperlinks enabled in terminal-features
  2. Run Claude Code inside tmux
  3. Observe that statusline hyperlinks are not clickable

Control tests (both pass):

  • Run printf '\033]8;;https://example.com\033\\Click\033]8;;\033\\\n' in the same tmux pane → clickable
  • Run Claude Code outside tmux with the same statusline → clickable

Environment: Claude Code 2.1.x, tmux 3.5a, Ghostty terminal, macOS

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.73

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

_No response_

View original on GitHub ↗

8 Comments

yurukusa · 5 months ago

Good root cause analysis. The OSC 8 sequences get clobbered by Ink's re-rendering.
Instead of using Claude Code's statusline, run your own status display in a tmux pane:

watch -n 1 'cat /tmp/claude-status-$$ 2>/dev/null || echo "waiting..."'

Then update it from a Notification hook:

printf '\e]8;;https://github.com\e\\GitHub\e]8;;\e\\' > /tmp/claude-status-$PPID
exit 0

This keeps the hyperlinks in a separate rendering context that Ink can't clobber.
Also ensure tmux passes OSC 8 through:

set -g allow-passthrough on
set -as terminal-features ',xterm-256color:hyperlinks'
chad-fossa · 4 months ago

Additional reproduction data from #45173

Ran a systematic test with a minimal repro script to isolate the issue. Confirming the root cause analysis in this ticket.

Repro script

#!/bin/bash
# /tmp/statusline-hyperlink-repro.sh
printf '\033]8;;https://github.com\033\\click-me\033]8;;\033\\'

Configure in ~/.claude/settings.json:

{
  "statusLine": {
    "type": "command",
    "command": "bash /tmp/statusline-hyperlink-repro.sh"
  }
}

Test matrix

| Context | Clickable? |
|---------|-----------|
| Script run directly in terminal (no tmux) | Yes |
| Script run directly in tmux pane | Yes |
| Claude Code statusline (no tmux) | Yes |
| Claude Code statusline (inside tmux) | No |
| Claude Code statusline (tmux, fresh session) | No |
| Regular hyperlinks in Claude Code output (inside tmux) | Yes |

Variants tested (all inside tmux, all still broken)

| Variant | Result |
|---------|--------|
| \e\\ (ST) as OSC 8 terminator | Not clickable |
| \a (BEL) as OSC 8 terminator | Not clickable |
| CLAUDE_CODE_NO_FLICKER=1 | Not clickable |
| CLAUDE_CODE_NO_FLICKER unset (default) | Not clickable |
| set -ga terminal-features ",xterm-256color:hyperlinks" in tmux.conf | Not clickable |
| set -g allow-passthrough on in tmux.conf | Not clickable |

Key observation

tmux fully supports OSC 8 — hyperlinks work in regular pane content AND in Claude Code's own output (e.g. PR links rendered in conversation). The issue is exclusively in the statusline rendering path, consistent with the analysis that statusline content bypasses Ink's React component tree where OSC 8 round-tripping is preserved.

Environment

  • Claude Code 2.1.96
  • Kitty terminal
  • tmux 3.6a
  • macOS
github-actions[bot] · 3 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

chad-fossa · 3 months ago

This is a bug -- I would suggest leaving it open and fixing it

chad-fossa · 3 months ago

I think somehow this is fixed now... I'm no longer having the issue

chad-fossa · 3 months ago
github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.