Repository navigation
Description
What happened
A headless t3 serve (background service) on a Mac mini is reached remotely in Safari through Tailscale Serve (https://<host>.ts.net:10000 → http://127.0.0.1:3773). The page often fails to load and shows "T3 Code failed to start" with:
TypeError: Importing a module script failed.
Version: 0.1.9 Channel: stable Surface: web
User agent: ... Version/27.2 Safari/605.1.15
Reloading sometimes works and sometimes doesn't. Opening the same server directly on 127.0.0.1:3773 always works. In practice, the web UI of a remote headless server can't be used reliably over Tailscale Serve.
Diagnosis
- A fresh load requests about 300 JS chunks at once:
main-*.jsplus its static dependencies, out of 959 inclient/assets. - Safari sends them over a single HTTP/2 connection. Tailscale Serve's Go reverse proxy opens a separate TCP connection to
127.0.0.1:3773for each one. - The backend's listen queue holds only 128 connections (macOS
kern.ipc.somaxconn= 128;netstat -Lanshows0/0/128 *.3773). Part of the burst is refused at connect. - Tailscale logs bursts of
http: proxy error: dial tcp 127.0.0.1:3773: connect: connection reset by peer(22 to 43 per load), plus someread: connection reset by peer. The refused requests never reach the T3 server, so they don't appear inserver.trace.ndjson. - The browser gets a 502 for those chunks, so
import("./main")inapps/web/src/bootstrap.tsrejects and goes straight toshowBootError. There is no retry on this path. Thevite:preloadError/reloadOnceForChunkLoadErrorguard lives inmain.tsx, which never loaded.
Contributing factors on the T3 side:
- The size of the startup chunk fan-out.
- No retry when the boot import fails.
- T3's own
--tailscale-servesets up exactly this proxy, so it's a supported path.
Not fully explained: a few quick reloads failed without the server seeing a request for main-*.js at all (index-*.js revalidated with 304, then nothing). Safari may be reusing the earlier failure.
Steps to reproduce
- On macOS, run
t3 serveon127.0.0.1:3773. - Expose it with
tailscale serve --bg --https=10000 http://127.0.0.1:3773. - From the server itself, fire about 300 parallel HTTP/2 requests for the bundled chunks through the Serve URL:
Result, three runs:
cd <runtime>/versions/0.1.9/client/assets ls *.js | head -300 | sed 's#^#url = "https://<host>.ts.net:10000/assets/#;s#$#"\noutput = "/dev/null"#' > /tmp/urls.cfg curl -s --http2 --parallel --parallel-max 300 -K /tmp/urls.cfg -w '%{http_code}\n' | sort | uniq -c
236/64,219/81,249/51(200/502). - Run the same 300 requests against
http://127.0.0.1:3773directly: all 300 return 200. - In a browser, open the Serve URL and reload a few times. The boot error appears intermittently.
Version
Server 0.1.9 (stable), background service (launchd). The triage CLI was 0.1.2; it was not involved.
Environment
- macOS 27.2.1, arm64 (Mac mini M4); server runs as a launchd service.
- Client: Safari 27.2 on macOS over the tailnet.
- Tailscale 1.104.5 (macOS app); Serve set up by hand, HTTPS port 10000 →
http://127.0.0.1:3773.
Evidence
Server trace for Safari loads through Serve (index-*.js status → whether main-*.js followed):
13:31:12 200 index -> main OK (Tailscale: 22 dial resets + 16 read resets at 13:31:12)
13:31:15 304 index -> NO main
13:31:16 304 index -> NO main
13:31:32 304 index -> NO main
13:31:33 304 index -> NO main
13:32:25 200 index -> main OK (29 dial resets)
13:32:51 200 index -> main OK (43 dial resets)
13:32:53 304 index -> NO main
13:34:29 304 index -> NO main (boot error reported at 21:34:29Z)
13:38:14 304 index -> main OK (8 dial resets + 20 read resets; this load worked)
Tailscale's unified log (process IPNExtension):
http: proxy error: dial tcp 127.0.0.1:3773: connect: connection reset by peer
http: proxy error: read tcp 127.0.0.1:54468->127.0.0.1:3773: read: connection reset by peer
http: proxy error: write tcp 127.0.0.1:54484->127.0.0.1:3773: write: broken pipe
All assets are served cache-control: no-cache, so every load revalidates the full chunk set.
Related issues
- Web: a chunk that fails after boot reloads the page in an endless loop #29304 (closed): chunk failures after boot cause a reload loop. Same reload-guard area, different trigger.
- [Bug]: Desktop: paused chat videos keep HTTP/2 streams open, and later requests to the same host stall until reload #27624: HTTP/2 stream stalls on the desktop. Possibly related to proxy and HTTP/2 behavior.
- [Bug]: Tailscale Serve failures are only logged as a warning; the server silently stays loopback-only #16238, Server update to 0.1.3-nightly.20261025.3692 rolls back with EADDRINUSE when Tailscale Serve shares the server's port #25161: other Tailscale Serve issues.
Fix applied or workaround
None applied. Possible directions:
- Retry the boot
import("./main")once or twice with a short backoff before showing the boot error. - Reduce the number of startup chunks, for example by merging the per-icon chunks.
- Set a larger listen backlog on the server socket. macOS still caps it at
kern.ipc.somaxconn, sosudo sysctl kern.ipc.somaxconn=1024is a possible local workaround (untested).
Filed by
Claude Opus 5.5 (claude-opus-5-5) in Claude Code, via t3 triage.
Activity