fix: make code <file> work in serve-web terminals#519
Merged
Conversation
The apt `code` package installs the desktop (Electron) binary at /usr/bin/code. In our headless serve-web/tunnel sessions there is no X display, so running `code <file>` from the integrated terminal crashes with "Missing X server or $DISPLAY" (plus a dbus warning). Add a small wrapper at $HOME/.local/bin/code (already first on the interactive PATH via .zshrc/.bashrc) that forwards to the VS Code serve-web/remote-cli shim, which opens files in the running window via $VSCODE_IPC_HOOK_CLI. It resolves the newest shim so it survives VS Code updates, and falls back with a clear message if no session is running. Only affects interactive shells; build-time `code --install-extension` still resolves to /usr/bin/code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the `code` wrapper to handle a stale $VSCODE_IPC_HOOK_CLI. The value is captured when the shell starts, so after a VS Code reconnect (container/server restart) it can name a socket that no longer exists, and `code <file>` fails. When — and only when — the referenced socket is gone, fall back to the most recently created /tmp/vscode-ipc-*.sock. A still-present socket is left untouched, so routing stays correct when multiple windows (each with its own socket) are connected. Dependency-free and no double-exec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-review hardening from a shell audit: `ls -t` can surface a shim path that lacks the execute bit, in which case `exec` fails with an opaque "exec: Permission denied". Add a `-x` check so that case takes the existing friendly "no usable shim" branch instead. Deliberately not adding a "fail if no live socket" guard: socket-less subcommands (code --version/--list-extensions/--help/tunnel) must still reach the shim, so socket presence is left for the shim to require. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fernandoataoldotcom
approved these changes
Jul 10, 2026
A long-lived shell captures $VSCODE_IPC_HOOK_CLI once. A tmux server that outlives a VS Code reconnect keeps handing every pane the socket of an OLD window. That socket usually still accepts connections, so `code <file>` "succeeds" but opens the file in a window the user can no longer see — the reported "code doesn't work in tmux" symptom. The prior fallback only triggered when the socket file was missing, so it didn't help here (the stale file still exists and connects). Since VS Code creates one socket per window/connection under /tmp, the newest is the current window. Select the newest socket that still accepts a connection instead of trusting the inherited value. A best-effort socat connect probe skips sockets whose window has gone away; if socat is missing the newest existing socket is used. When no socket is reachable the inherited value is left as-is so socket-less subcommands (code --version, --list-extensions, tunnel) keep working. Add socat to the image so the probe is deterministic. Verified end to end: with the stale tmux socket forced into the environment, the wrapper selects the newest live socket and the file opens in the active window. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a passthrough so `code tunnel` and `code serve-web` (full-CLI subcommands the remote-cli shim does not implement — it would treat "tunnel" as a filename) exec the real /usr/bin/code, which dispatches them to its bundled CLI and runs headless. Everything else keeps routing through the shim. Bound the socket liveness probe with `timeout 2` so a pathological connect() can never wedge `code`. Document the single-window limitation of the "newest socket" heuristic inline. Validated in a full `devcontainer build` of this image via docker exec: tunnel/serve-web show their real help; socket selection skips a dead-but-newest socket, picks the newest live one, overrides a stale inherited VSCODE_IPC_HOOK_CLI (the tmux case), and falls through without hanging when nothing is live. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
code <file>from the integrated terminal fails two ways in this serve-web environment:coderesolves to the desktop Electron binary/usr/bin/code, which needs an X display and dies withMissing X server or $DISPLAY.$VSCODE_IPC_HOOK_CLIat an old window's socket. That socket still accepts connections, socode"succeeds" but opens the file in a window you can no longer see.Fix
A wrapper at
$HOME/.local/bin/code(first on the interactivePATH) that forwards to the serve-web remote-cli shim. It:ls -t, survives VS Code updates;-xguard → clear error instead of opaqueexecfailure)./tmp; the newest live one is your current window. Asocatconnect probe (bounded bytimeout 2socodecan never hang) skips dead sockets left by restarts; if nothing is reachable the inherited value passes through so socket-less commands still work.tunnel/serve-webthrough to/usr/bin/code— these are full-CLI subcommands the remote-cli shim doesn't implement (it would treattunnelas a filename). Uses the absolute path to avoid recursing into the wrapper.socatis added to the image for the probe. Only affects interactive shells — build-timecode --install-extensionruns under/bin/sh(no~/.local/binonPATH) and still uses/usr/bin/code.Known limitation
With multiple windows open at once, "newest socket" may not be the exact window you typed in — serve-web exposes no API to map a socket to a window (
--statusis unsupported in browsers; every live socket answers a connect). This is the best available heuristic and matches community practice (vinnie/tmux, chvolkmann/code-connect); it is correct for the common single-window and stale-tmux cases. Documented inline in the wrapper.Review & validation
Reviewed by four independent experts (POSIX sh/dash, Linux/sockets, VS Code serve-web internals, Docker build) — no blocking issues; their fixes (executability guard, probe timeout, tunnel/serve-web passthrough) are folded in.
Validated against a full
devcontainer buildof this image, exercised viadocker exec:sh -nclean;coderesolves to it via.bashrc.code tunnel --help/code serve-web --help→ real tunnel/serve-web help (exit 0).VSCODE_IPC_HOOK_CLI(the tmux fix), and falls through without hanging when nothing is live.🤖 Generated with Claude Code