Skip to content

fix(box): links a program draws as text, like "Authorize with Codex", open with ⌘-click - #49

Merged
sean-brydon merged 1 commit into
mainfrom
fix/osc8-links
Oct 11, 2026
Merged

sean-brydon merged 1 commit into
mainfrom
fix/osc8-links

Conversation

@sean-brydon

Copy link
Copy Markdown
Collaborator

What was broken

In a box's terminal, fx's "Sign in with Codex" screen shows Open Authorize with Codex, where "Authorize with Codex" is an OSC 8 hyperlink. It showed underlined, but ⌘-click opened nothing. Plain-text addresses already worked after #48.

Cause, from the bytes

I captured on devl (tmux 3.6, fx 0.0.13, not signed in) with a separate tmux server running fx under a copy of berthd's tmux.conf, then recorded what a client attached the way berthd attaches the app (tmux -u … attach, TERM=xterm-256color) receives.

  • fx writes the link correctly. capture-pane -e shows
    ESC[4m ESC]8;id=fx-codex-auth;https://auth.openai.com/oauth/authorize?…&originator=fx ESC\ Authorize with Codex ESC[0m ESC]8;; ESC\
  • With berthd's config, the attached client gets no OSC 8 at all. The stream has 0 ESC]8; and just Open ESC[4mAuthorize with Codex ESC(B ESC[m ESC[K: the underline, with no address.
  • With terminal-features including xterm*:hyperlinks, the same attach gets the link whole:
    ESC[4m ESC]8;id=tmux3;https://auth.openai.com/oauth/authorize?… ESC\ Authorize with Codex ESC]8;; ESC\ ESC(B ESC[m ESC[K
  • tmux list-clients on berthd's server shows the app's real client for the fx session as xterm-256color features=bpaste,ccolour,clipboard,cstyle,focus,title, without hyperlinks.
  • tmux could only learn the feature from the terminal's answers to ESC[c, ESC[>c and ESC[>q (XTVERSION), which it sends on attach. ghostty-web 0.4 answers none of them (it answers DSR ESC[6n/ESC[5n only; checked against its WASM in node).

So the app's OSC 8 tracker from #48 never saw an address to remember. Two smaller things also showed up. ghostty-web 0.4's hyperlink_id is 1 for every link cell, so it marks a cell as part of some link and doesn't tell links apart. And tmux redraws a link in pieces when only some of its cells change, so the text it sends with the link can be a piece, not the whole label.

Fix

  • box: berthd's tmux.conf adds set -s terminal-features[90] "xterm*:hyperlinks" inside %if "#{m/r:^(next-|[4-9]|3\.([4-9]|[1-9][0-9])),#{version}}". Hyperlinks came in tmux 3.4. I checked the guard in Docker:

    • 3.0a (Ubuntu 20.04) has no terminal-features option and skips the line without a config error.
    • 3.2a and 3.3a skip it.
    • 3.4 (Ubuntu 24.04), 3.6 (devl) and 3.7c (Shipyard's own tmux, macOS) turn it on.

    The fixed index keeps it to one entry when berthd re-sources the file into a running server.

  • app, hardening: HyperlinkTracker now remembers a link's text in the pieces it was drawn in: a new piece starts when the cursor moves or the line breaks while the link is on. uriFor also matches text made up of a link's pieces (inPieces). The OSC 8 provider merges a run into the one before it when they touch and share an address. Hovering any word of "Authorize with Codex" gives the whole label as one link, and ⌘-click opens the full address.

Tests

  • Go: TestAttachPassesOSC8Hyperlinks attaches to a session that prints an OSC 8 link and expects it in the client's bytes. It fails without the config change, and skips on tmux older than 3.4.
  • Unit (term-links.test.ts): fx's own bytes and tmux's attach bytes as captured, with a made-up address. Any word of the label gives the address; the SGR-only bytes berthd's tmux used to send give none. A link drawn in pieces with cursor moves, re-opened per piece, or across a line break is found; text only partly made of a link's pieces is not.
  • e2e (terminal-links.spec.ts): replays the attach-shaped screen and an "in pieces" screen (each piece its own tmux hyperlink, second piece first). Hovering at "Authorize", "with" or "Codex" gives the whole label as the link range, ⌘-click opens the full address, and "Open" doesn't. The "in pieces" case fails on main's app code.

Checks

  • go vet ./..., go test -race ./internal/box/
  • pnpm typecheck:plugins && pnpm check:titles && pnpm check:csp && pnpm check:themes && pnpm test && pnpm build, npx tsc -p e2e
  • E2E_PORT=1426 npx playwright test e2e/terminal-links.spec.ts e2e/home-terminal.spec.ts: 10 passed. The new tests also passed with --repeat-each=5.
  • docs-site build (changelog)

Boxes need the new berthd. On start it re-sources tmux.conf into the running server, so the next attach (reopen the terminal or reconnect) gets links; already-attached clients keep their old features. Boxes on tmux older than 3.4 can't pass OSC 8 at all.

🤖 Generated with Claude Code

… open with ⌘-click

berthd's tmux dropped OSC 8 hyperlinks on the way to the app: the app
attaches as xterm-256color, tmux's terminal-features don't give xterm
the hyperlinks feature, and ghostty-web doesn't answer the DA/XTVERSION
queries tmux could learn it from. tmux 3.4+ now gets
terminal-features[90] "xterm*:hyperlinks" (guarded by version, so older
tmux skips it). In the app, a link's text is remembered in the pieces
tmux draws it in, and adjacent runs with the same address are one link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
berth Ready Ready Preview Oct 10, 2026 12:08pm UTC
berth-docs Building Building Preview Oct 10, 2026 12:08pm UTC

Request Review

@sean-brydon
sean-brydon merged commit 1af5225 into main Oct 11, 2026
8 checks passed
@linear-code

linear-code Bot commented Oct 11, 2026

Copy link
Copy Markdown

ENG-5889

This branch was successfully deployed

2 active deployments
Preview – berth-docs — 0e373fd2 Deployed Oct 10, 2026 by vercel[bot]
Preview – berth — 0e373fd2 Deployed Oct 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant