mattpocock / mattpocock/evalite
UI fails on any remote access: bundle hardcodes localhost + server has no configurable bind host
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1.7k
- Forks
- 102
- PR merge metrics
- No merged PRs in 30d
Description
Summary
Evalite's watch-mode UI is unreachable from any device that isn't the same host running the server — through Tailscale, cloudflared, ngrok, an SSH tunnel to a different machine, a VPS + reverse proxy, etc. Two independent bugs combine to cause it.
Reproduction
- Run
evalite watchon host A (e.g. a VPS, remote dev machine, cloud IDE) - Expose port 3006 to host B (Tailscale,
ssh -L, cloudflared, ngrok — any forwarding mechanism) - Open the UI URL from host B in a browser
- HTML loads. The UI shows "Something went wrong" / "Load failed"
Root causes
Bug 1 — bundle hardcodes localhost
dist/ui/assets/index-*.js builds its API and WebSocket URLs from a hardcoded string, not from window.location. Two occurrences:
// current (v0.19.0):
ws://localhost:${X0}/api/socket
http://localhost:${X0}
On any client that isn't the same machine as the server, localhost resolves to the client itself → every fetch and WebSocket connection fails → "Load failed".
Fix:
ws://${location.host}/api/socket
http://${location.host}
Works for both local (location.host === "localhost:3006") and remote (location.host === "myhost.example.com:8080").
Bug 2 — server hardcoded to loopback bind, no config option
packages/evalite/dist/server.js around line 357:
server.listen({
port,
}, (err) => { ... });
Fastify defaults to 127.0.0.1 when host is unset. No EVALITE_HOST env var, no serve.host config option. Users can't bind to a specific tailnet IP, or 0.0.0.0 for use behind a firewall, etc.
Fix (minimal, backwards-compatible):
server.listen({
port,
host: process.env.EVALITE_HOST || '127.0.0.1',
}, (err) => { ... });
Default stays loopback — no regression for existing users. Anyone needing to expose the UI sets EVALITE_HOST (or better, add server.host to the config schema).
Workaround (what I'm running now)
patch-package with a patch that applies both fixes. Details + full patch file: Saldoapp PR #315. Works fine, but requires the minified bundle diff (~200 KB) to be tracked in the consumer repo — unavoidable while the assets ship pre-built.
Impact
Any team using evalite from:
- A dev VPS with an iPad/phone iterating over Tailscale (my case)
- A cloud IDE (GitHub Codespaces, Gitpod) → local browser
- A remote pair via
ssh -L - A shared team dashboard behind a reverse proxy
…hits both bugs and has to either give up remote access or maintain a fork/patch. First bug (the bundle) is the harder one — no runtime workaround since the assets are pre-built and shipped.
Environment
- evalite
0.19.0(latest) - Node 20
- Verified on Linux (Debian) VPS ↔ iPad Safari over Tailscale
Happy to send a PR
The bundle change is a 2-line source edit; the server change is 1 line + a config schema field. Can do both if it'd help. Also happy to just leave this as an issue and let you fix it however fits best.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the source that produces dist/ui/assets/index-*.js and inspect packages/evalite/dist/server.js around server.listen. Reproduce with evalite watch through a forwarded or remote URL, then verify that the remote UI loads and the server supports the requested bind host while retaining the existing default.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- backend, full-stack, networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 65/100