Shift+Enter newline, OAuth URL truncation, and the auth bridge toggle

Three fixes that all land on the same journey: sign in, paste a prompt,
and have the terminal behave the way every other Claude Code host does.

Shift+Enter inserts a newline
-----------------------------
xterm.js does not consult `shiftKey` for Enter (`Keyboard.ts`, case 13),
so Shift+Enter was byte-identical to Enter and submitted the prompt.
Both terminals now send `\x1b\r` (ESC+CR) instead, which Claude Code
parses as return+meta — the same bytes its own `/terminal-setup` writes
into the VS Code, Cursor, Alacritty and Zed keymaps, so this is in-band
rather than a guess. Not `\n`: Claude Code accepts it, but a shell would
run the line, so the two session types would diverge. Bound in Claude
sessions only for that reason.

`entrypoint.sh` sets `shiftEnterKeyBindingInstalled` in `~/.claude.json`
so the CLI stops printing its "run /terminal-setup" tip. Purely
cosmetic — the decoding is unconditional either way.

Alt+Enter has always done the same thing (xterm ESC-prefixes on altKey)
and was simply never documented. It is now, along with the rest.

OAuth login URL truncation
--------------------------
Two producers wrote one toast slot, last-writer-wins. The OSC 7777 relay
delivers the URL base64-encoded and therefore exact; ~300 ms later the
screen-scraper's debounce fired and overwrote it with a truncated guess
at the same link — a URL that parses, points at the right host, and
authorises nothing. The user is the one who has to notice.

Why the scraper truncated: `ANSI_RE` strips OSC sequences wholesale,
including the OSC 8 hyperlink whose parameter carries the complete URL.
Claude Code slices the *visible* text of that hyperlink to the terminal
width while every emission carries the whole URL in its parameter. The
backend already knew this (`commands/auth_token_commands.rs`); the
frontend did not.

- `urlDetector` now reads OSC 8 targets out of the raw buffer before
  stripping, filtered by a port of `usable_sign_in_link`, and tags every
  candidate with its provenance.
- The prompt slot gained `supersedes`: better provenance always wins,
  worse never does, and between equals only a candidate that *extends*
  what is showing may replace it. That last rule is `extendsUrl`,
  factored out of `pickSignInUrl` rather than copied — same rule, same
  reason, one implementation.
- `flatten` splits on a bare `\r` as well as on `\r?\n`, so a
  `\r`-repainted TUI frame no longer inflates a line past the width and
  suppresses a join that should have happened; and the width is now
  sampled at `feed()` rather than read at `scan()`, so a resize inside
  the 300 ms debounce cannot reassemble 80-column text against a
  120-column rule.

Also corrects the comment claiming `acquire_claude_token` enables the
auth bridge. It deliberately does not, and the module comment in
`auth_token_commands.rs` explains at length why not.

The auth bridge toggle
----------------------
`setAuthBridgeEnabled` and `getAuthBridgeStatus` had zero call sites:
the Rust was complete, the IPC wrapper shipped, and there was nowhere to
click — so the docs told users to "enable the Auth Bridge" for a switch
that did not exist. `AuthBridgeRow` is that switch, in Config → Runtime.
It deliberately does not go through the tab's stopped-only save: the
dedicated command exists so the bridge can be flipped while a login is
hanging in a running container, which is the only moment anyone reaches
for it.

It also subscribes to `auth-bridge-changed`, which the poller has been
emitting to nobody — so a host port the bridge could not take was a
completely silent failure, indistinguishable from a login that hung.

`tunnel.rs` promotes the best-effort `::1` bind failure from debug to a
warning recorded on the port. Half-bound is the failure mode that looks
like success: the status says bridged, and a client that resolves
`localhost` to `::1` without falling back is still refused.

Finally, for a recognised Anthropic sign-in URL the toast now leads with
"In container" and demotes the host "Open". The callback listener is
inside the container, so the container-side browser closes the loop with
no host round trip and no auth bridge; the host button stays as the
fallback. Ordinary URLs are unchanged.

Tests: 402 frontend (was 359), 285 Rust (unchanged).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
This commit is contained in:
2026-08-23 08:31:39 -07:00
co-authored by Claude Opus 5
parent 75cace7dde
commit 22d142c70d
21 changed files with 1529 additions and 98 deletions
+40 -4
View File
@@ -128,8 +128,11 @@ Anthropic-backend project uses that token without its own login. See
2. Claude prints an OAuth URL. Triple-C detects long URLs and shows a clickable toast at the top of the terminal — click **Open** to open it in your browser.
3. Complete the login in your browser. The token is saved and persists across container stops, starts and recreations. A **Reset** deletes it — see below.
> If the login hangs after the browser step, the callback could not reach the container. Enable the
> [Auth Bridge](#browser-logins-inside-the-container-auth-bridge) for that project.
> If the login hangs after the browser step, the callback could not reach the container. Either
> click **In container** on the toast instead of **Open** — the callback then never has to leave the
> container at all — or turn on the
> [Auth Bridge](#browser-logins-inside-the-container-auth-bridge) in the project's
> **Config → Runtime** section.
**AWS Bedrock:**
@@ -789,6 +792,19 @@ web server they started on `localhost`. `claude login`, `aws sso login` and Conc
The **Auth Bridge** fixes this. It is **opt-in per project** and **off by default**.
### Where the switch is
Project Home → **Config****Runtime****Auth bridge**.
Unlike the rest of that tab, it is **not** greyed out while the container is running — it is a
host-side feature that recreates nothing, and the moment you want it is usually the moment a login
is already hanging in a running container. Switch it on, then retry the login.
Beside the switch is its live state: **Off**, **Watching** (on, nothing to bridge yet — normal,
there is only something to bridge while a login is waiting), **Bridging *n* ports**, **IPv4 only**,
or **Port conflict** with the port and the reason. A conflict means the host port was already taken
and the callback will not arrive; free the port, or use **In container** instead.
### What it does
- Every couple of seconds it looks inside the container for programs listening on the container's
@@ -1293,9 +1309,19 @@ triple-c-scheduler add --name "test" --schedule "0 */6 * * *" --prompt "Run test
| **Ctrl+Shift+V** | Paste |
| **Ctrl+V** | Paste an image from the clipboard into the container |
| **Ctrl+Shift+M** | Toggle speech-to-text recording (when enabled) |
| **Shift+Enter** | Insert a newline in Claude Code's prompt instead of submitting it |
| **Alt+Enter** | The same thing, and it has always worked — it was simply never written down |
Everything else goes straight through to the program running in the container.
> **Shift+Enter** sends `ESC` + `CR`, the same bytes Claude Code's own `/terminal-setup` installs
> for VS Code, Cursor, Alacritty and Zed — so there is nothing to run and no tip to follow. It is
> bound in **Claude** tabs only: in a **bash** tab that sequence means nothing to readline, and
> Shift+Enter there submits the line as it always has.
>
> In the [Web Terminal](#web-terminal-remote-access) the same chord works, and there is an **↵+**
> key beside **Enter** on the mobile key row for devices with no Shift.
---
## What's Inside the Container
@@ -1402,8 +1428,18 @@ your machine (anything that isn't `http`/`https`).
You opened the URL, signed in successfully, and the CLI in the terminal is still waiting. The
callback from your browser is landing on your host's `localhost` while the CLI is listening on the
*container's*. Enable the
[Auth Bridge](#browser-logins-inside-the-container-auth-bridge) for that project and try again.
*container's*.
Two ways out, in order of least effort:
1. Dismiss and re-trigger the login, then click **In container** on the toast rather than **Open**.
The page opens in a browser *inside* the container, so the callback never has to cross to the
host. This needs no auth bridge — only a running container with Playwright installed (Project
Home → **Browser**). For a recognised Anthropic sign-in link this is already the default button.
2. Turn on the [Auth Bridge](#browser-logins-inside-the-container-auth-bridge) — Project Home →
**Config****Runtime****Auth bridge** — and try again. It can be switched on while the
container is running. Check the indicator beside it: **Port conflict** means the host port was
already taken and the callback still will not arrive.
For Claude specifically, the simpler answer is usually
[Shared Claude Authentication](#shared-claude-authentication), which finishes on an Anthropic-hosted