Modal Tunnel Pairing URL and QR Code in T3 Code CLI¶
Status: Accepted (Amends ADR 0025, ADR 0035, and ADR 0028)
We ensure that running t3 pair inside Modal sandboxes provisioned with --t3 or --t3code prints the public Modal HTTPS tunnel URL (e.g. https://<sandbox-tunnel>.modal.host/pair#token=...) and encodes the HTTPS URL into the terminal QR code, matching the sandbox launch output and enabling immediate pairing from mobile apps and external browsers.
Context¶
When launching Modal sandboxes with --t3 or --t3code, Modal establishes an encrypted application tunnel on port 3775 and prints an instant pairing link and QR code on the host (t3_pair_url = clean_url + "/pair#token=" + token).
However, when developers connected into the sandbox via SSH or opened a terminal session and executed t3 pair, T3 Code inspected /root/.t3/userdata/server-runtime.json. If devUrl was missing, T3 Code fell back to inspecting container network interfaces via NodeOS.networkInterfaces(), generating a private IP address and unroutable QR code:
Because 172.20.x.x is an internal private IP within Modal's virtualization environment, mobile devices scanning the QR code and external browsers could not reach the server. Additionally, executing t3 pair from within a repository worktree failed with NoRunningServerError unless --base-dir /root/.t3 was manually specified.
Considered Options¶
- Node SEA Bytecode Patching (
/opt/t3/t3): Modifying the single-executable Node binary'sresolveDirectPairingBaseUrlfunction in place via length-preserving string replacements (similar to IPv6 and SSH hostname patches in ADR 0035). Rejected because reading public URLs from environment variables or/root/.t3/public_urlin JavaScript within exact byte budgets is fragile, prone to SEA offsets corruption, and breaks across T3 Code binary upgrades. - Vite Dev Server Flag (
t3 serve --dev-url <url>): Passing the tunnel URL as--dev-urlat server startup. Rejected because Modal tunnel URLs are only allocated by the host after container creation begins, and--dev-urlinstructs T3 Code server to disable its bundled static web client assets in favor of proxying to an external Vite development server. - Intelligent CLI Wrapper (
/usr/local/bin/t3) and Dual-Layer State Synchronization: - Baking
/usr/local/bin/t3as a wrapper script inshared/image.py(andshared/t3-wrapper.sh) delegating to/opt/t3/t3. - Resolving the public tunnel URL from
T3_PUBLIC_URLor/root/.t3/public_urland stampingdevUrlinto/root/.t3/userdata/server-runtime.json. - Running a 60-second one-shot background watcher in
shared/startup.shat container boot to stampdevUrlas soon as bothpublic_urlandserver-runtime.jsonappear. - Automatically supplying
--base-dir /root/.t3whent3 pairis called inside subdirectories and worktrees without--base-dir. Accepted.
Consequences¶
- Image Staging (
shared/image.py&shared/t3-wrapper.sh): - The wrapper script is pre-installed at
/usr/local/bin/t3during base image creation and retained in snapshots. - Ensures clean delegation to
/opt/t3/t3. - Startup Script (
shared/startup.sh): - Launches a bounded one-shot background loop (up to 60s) stamping
devUrlintoserver-runtime.jsononcet3 servestarts. - Leaves no lingering background daemon after initialization finishes.
- Worktree Usability:
- Developers can run
t3 pairfrom anywhere in the filesystem (including project root and git worktrees) without manually passing--base-dir /root/.t3. - User-specified options (such as custom
--base-diror--ttl) are strictly preserved. - Test Integrity:
- Corrected heredoc delimiter isolation in
tests/test_sandbox/test_t3_code.pyto prevent unit tests from modifying local host binary paths. - Added dedicated unit test suite in
tests/test_sandbox/test_t3_pair.py. - Universal File Line Budget:
- All modified files remain strictly compliant with the repository line budget ratchet (ADR 0005 / ADR 0027).