Skip to content

Instantly share code, notes, and snippets.

@xpn
Created April 22, 2026 15:04
Show Gist options
  • Select an option

  • Save xpn/74e0475cda48f26d653aab5b5a959ca2 to your computer and use it in GitHub Desktop.

Select an option

Save xpn/74e0475cda48f26d653aab5b5a959ca2 to your computer and use it in GitHub Desktop.

VS Code Dev Tunnels: Initial Notes

Workspace

  • Cloned microsoft/vscode into ./vscode
  • Cloned microsoft/dev-tunnels into ./dev-tunnels

High-Level Architecture

  1. VS Code desktop UI collects an auth session from a configured provider (github or microsoft).
  2. The shared-process RemoteTunnelService spawns the bundled code-tunnel CLI.
  3. The CLI logs in, resolves or creates a dev tunnel, tags it, and hosts reserved ports over the tunnel.
  4. Remote clients discover those tunnels by label and connect through the Dev Tunnels relay.

Main VS Code Entry Points

  • UI / account selection:
    • vscode/src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts
  • Shared-process tunnel lifecycle:
    • vscode/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts
  • CLI tunnel orchestration:
    • vscode/cli/src/commands/tunnels.rs
  • CLI auth flow and token storage:
    • vscode/cli/src/auth.rs
  • CLI tunnel management client and tunnel tagging:
    • vscode/cli/src/tunnels/dev_tunnels.rs
  • Agent-host tunnel discovery / connection:
    • vscode/src/vs/platform/agentHost/node/tunnelAgentHostService.ts

Authentication Findings

  • VS Code UI only offers providers configured in product.json and registered with the authentication service.
  • The shared process passes the selected access token into the CLI via VSCODE_CLI_ACCESS_TOKEN.
  • The CLI supports both:
    • interactive device-code login
    • direct token injection (--access-token, --refresh-token, env-backed)
  • Device-code endpoints in the CLI:
    • Microsoft device code: https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode
    • Microsoft token: https://login.microsoftonline.com/organizations/oauth2/v2.0/token
    • GitHub device code: https://github.com/login/device/code
    • GitHub token: https://github.com/login/oauth/access_token
  • Credential persistence:
    • preferred: OS keyring
    • fallback: token.json in the CLI state root

Tunnel Discovery / Identity

  • VS Code launcher tunnels are identified with the label vscode-server-launcher.
  • Tunnel labels also carry:
    • the human-readable tunnel name
    • a protocol tag like protocolvN
    • platform flags like _flag...
  • Name parsing on the agent-host side treats the first non-system label as the display name.
  • Reserved ports:
    • 31545: control port
    • 31546: agent-host port

Tunnel Lifecycle Behavior

  • code tunnel status returns JSON with:
    • whether a service is installed
    • whether a tunnel process is already running
  • When starting a tunnel, VS Code:
    • optionally runs code tunnel user login
    • optionally installs the tunnel as a service
    • runs code tunnel --accept-server-license-terms ...
  • The CLI reuses persisted tunnel metadata from:
    • code_tunnel.json
    • port_forwarding_tunnel.json
  • If a persisted tunnel is missing or forbidden, the CLI creates a new one.
  • When creating or looking up tunnels, the CLI requests scoped access tokens from the service, especially host and connect.

Dev Tunnels SDK Findings

  • The standalone SDK surface needed for a custom tool already exists in microsoft/dev-tunnels.
  • Relevant TypeScript APIs:
    • TunnelManagementHttpClient.listTunnels()
    • TunnelManagementHttpClient.getTunnel()
    • TunnelManagementHttpClient.createTunnel()
    • TunnelManagementHttpClient.updateTunnel()
    • TunnelManagementHttpClient.deleteTunnel()
  • Request filtering and enrichment are driven by TunnelRequestOptions:
    • labels
    • requireAllLabels
    • includePorts
    • includeAccessControl
    • tokenScopes
  • The SDK distinguishes:
    • user auth headers from the userTokenCallback
    • tunnel-scoped tokens from TunnelRequestOptions.accessToken or tunnel.accessTokens

Remote Agent Host Details

  • Discovery path:
    • list tunnels with label vscode-server-launcher
    • require protocol version >= 5
    • request connect token scope
  • Connection path:
    • resolve the tunnel by { tunnelId, clusterId }
    • connect through TunnelRelayTunnelClient
    • wait for forwarded port 31546
    • open a WebSocket over that stream
  • The connection token used by the agent-host path is derived from sha256(tunnelId) and base64url-encoded.

Likely Path For A Standalone Tool

  1. Use the Dev Tunnels SDK directly, not the VS Code CLI, for list/create/update/delete operations.
  2. Reuse existing GitHub / Microsoft user tokens and pass them as the user auth header to the management client.
  3. Use labels plus tokenScopes to discover VS Code-created tunnels and request the right per-tunnel access tokens.
  4. If you need to attach to the VS Code agent host, replicate the port 31546 + WebSocket relay flow.

Open Questions To Dig Next

  1. Exact auth-header conventions for GitHub tokens across the Rust CLI and TypeScript SDK wrappers.
  2. Which tunnel labels are stable API contract vs. VS Code-specific convention.
  3. Whether existing tunnel-host tokens can be safely recovered from local state without reauthenticating.
  4. Which service endpoints or SDK calls are enough to fully manage tunnels without invoking the VS Code CLI at all.

Layer Map: Relay To Shell

Layer 0: VS Code chooses auth and launches the tunnel host

  • UI picks a session token from the selected provider:
    • vscode/src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts
  • Shared process passes the token into the Rust CLI through VSCODE_CLI_ACCESS_TOKEN:
    • vscode/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts

Layer 1: Tunnel management API resolves endpoints and host tokens

  • The CLI uses the Rust dev-tunnels crate to create/update the relay endpoint and obtain host access:
    • vscode/cli/src/tunnels/dev_tunnels.rs
    • dev-tunnels/rs/src/connections/relay_tunnel_host.rs
  • The agent-host client path uses the TS SDK to resolve the tunnel and request connect scope:
    • vscode/src/vs/platform/agentHost/node/tunnelAgentHostService.ts

Layer 2: Initial relay network connection is WebSocket + custom SSH profile

  • Host side opens a websocket with:
    • Sec-WebSocket-Protocol: tunnel-relay-host
    • Authorization: tunnel <host_token>
    • dev-tunnels/rs/src/connections/relay_tunnel_host.rs
  • Official client side uses:
    • tunnel-relay-client
    • tunnel-relay-client-v2-dev
    • dev-tunnels/ts/src/connections/tunnelRelayTunnelClient.ts
  • Important non-standard behavior:
    • the outer SSH session prefers kex=none, key=none, cipher=none
    • the Rust source explicitly says this is non-standard and breaks many off-the-shelf SSH stacks
    • dev-tunnels/rs/src/connections/relay_tunnel_host.rs
    • dev-tunnels/ts/src/connections/tunnelRelayTunnelClient.ts

Layer 3: Relay multiplexes client sessions and forwarded ports over SSH channels

  • After the outer relay SSH session is up, the service opens a channel of type:
    • client-ssh-session-stream
  • Inside that channel is another SSH session that carries port forwarding.
  • Port forwarding uses normal SSH requests/channels:
    • tcpip-forward
    • forwarded-tcpip
  • References:
    • dev-tunnels/rs/src/connections/relay_tunnel_host.rs
    • dev-tunnels/ts/src/connections/tunnelRelayTunnelHost.ts

Layer 4: VS Code CLI exposes two private forwarded ports inside the tunnel

  • Fixed ports:
    • 31545 control port
    • 31546 agent-host port
  • Protocol version tag is currently protocolv5.
  • References:
    • vscode/cli/src/constants.rs
    • vscode/cli/src/tunnels/control_server.rs

Layer 5A: Control port (31545) is a msgpack RPC control plane

  • The CLI accepts raw forwarded connections on 31545.
  • Frames are msgpack objects, not HTTP and not plain JSON.
  • RPC includes:
    • challenge_issue
    • challenge_verify
    • serve
    • servermsg
    • callserverhttp
    • forward / unforward
    • spawn / spawn_cli
    • file and socket helpers such as fs_read, fs_write, fs_connect, net_connect
  • References:
    • vscode/cli/src/tunnels/control_server.rs
    • vscode/cli/src/msgpack_rpc.rs
    • vscode/cli/src/tunnels/protocol.rs

Layer 5B: Control-port auth is separate from relay auth

  • The relay websocket auth only gets you onto the tunnel.
  • The control RPC can still require a challenge/response.
  • Non-vsda builds use:
    • 16-char random challenge
    • response is base64url(sha256(challenge))
  • Reference:
    • vscode/cli/src/tunnels/challenge.rs

Layer 6: serve bootstraps or attaches to the real VS Code server

  • The control RPC method serve resolves the right server build, starts it if needed, and attaches a bridge to its local socket.
  • That bridge turns the control channel into servermsg proxy traffic.
  • References:
    • vscode/cli/src/tunnels/control_server.rs
    • vscode/cli/src/tunnels/code_server.rs

Layer 7: The actual VS Code server has its own websocket handshake

  • Once the client is talking to the VS Code server, there is another handshake:
    • client sends auth
    • server replies sign
    • client sends connectionType
  • Supported connection types include:
    • Management
    • ExtensionHost
    • Tunnel
  • References:
    • vscode/src/vs/platform/remote/common/remoteAgentConnection.ts
    • vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts

Layer 8: Terminal/shell interaction is not a raw tunnel shell

  • After the VS Code server connection is up, terminal traffic goes through the remote IPC channel remoteterminal.
  • The remote server delegates terminal creation/data to PtyHostService.
  • The pty host launches TerminalProcess, which uses node-pty.spawn(...).
  • References:
    • vscode/src/vs/server/node/serverServices.ts
    • vscode/src/vs/server/node/remoteTerminalChannel.ts
    • vscode/src/vs/platform/terminal/node/ptyService.ts
    • vscode/src/vs/platform/terminal/node/terminalProcess.ts

Practical consequence

  • “Connect to the dev tunnel” and “interact with a shell like VS Code” are not one protocol.
  • They are a stack:
    1. Dev Tunnels relay websocket
    2. Outer relay SSH session with non-standard none negotiation
    3. Forwarded private tunnel ports
    4. CLI msgpack control RPC on 31545
    5. VS Code server bridge
    6. VS Code websocket handshake
    7. Remote terminal IPC
    8. node-pty shell process

The most likely incompatibility point for third-party implementations is Layer 2, followed by the private control protocol on Layer 5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment