local-url
Skill0 starsChanged 19 days agoMITView on GitHub
Give a locally served web app a bookmarkable address like http://<app>.localhost:<port>, with no /etc/hosts entry and no sudo, while every address that already worked (127.0.0.1, localhost, a Tailscale serve URL) keeps working. Use when the user wants a local URL, a nicer name than localhost:port, something to bookmark or favorite for a dev server or always-on local app, or invokes /local-url.
In this repository14 other files it gives its agents.
- ambitiousSkill
- attributionSkill
- ggSkill
- grill-meSkill
- guide-meSkill
- htmlSkill
- jfdiSkill
- flushcacheSkill
- scan-secretsSkill
- scpushSkill
- sendmailSkill
- social-previewSkill
- stern-code-reviewSkill
- strunkSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 20
No public DNS name can claim it, so admitting it to a Host guard does not reopen DNS rebinding.
- Distinctive: line 50
Prefer one exported constant for the name over repeating the string.
- Very specific: line 80
Never funnel, which makes it public.
- Very specific: line 92
Don't bind 0.0.0.0 to "make it work".
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Local URL
Goal: http://<name>.localhost:<port> opens the app from a bookmark on this machine.
The server keeps its bind address and port; nothing that worked before stops working.
Why .localhost
- RFC 6761 reserves
localhostand every name under it for loopback. Chrome and Firefox answer*.localhostthemselves, and the macOS resolver does too, so Safari, curl and the rest get 127.0.0.1 / ::1 with no hosts file, no sudo, no DNS. - No public DNS name can claim it, so admitting it to a Host guard does not reopen DNS rebinding.
- Not
.local: that is mDNS/Bonjour, with slow lookups and name collisions.
Steps
- Find what serves the app: port, bind address, how it runs (npm script, launchd
or systemd unit, Docker, framework dev server). Pick the name: the app's short
name, lowercase, e.g.
symposium.localhost. Keep the port; dropping it means port 80, which only one app on the machine can have. - Ask the machine before touching code:
dscacheutil -q host -a name <name>.localhost # macOS: expect 127.0.0.1 and ::1 getent hosts <name>.localhost # Linux; if empty, the user adds # "127.0.0.1 <name>.localhost" to /etc/hosts curl -s -o /dev/null -w '%{http_code}\n' http://<name>.localhost:<port>/- 200: it already works; skip to step 5.
- 400, 403, "Invalid Host header", "Blocked request": a host allow-list; step 3.
- Connection refused: nothing listens on the address the name resolved to; see Pitfalls.
- Admit the exact name (not a
*.localhostwildcard) wherever hosts are checked:- Custom middleware: grep for
headers.host,allowedHosts,Host header. - Framework knobs: Vite
server.allowedHosts, webpack-dev-serverallowedHosts, DjangoALLOWED_HOSTS, Railsconfig.hosts, Next.jsallowedDevOrigins. - Anything keyed on origin needs
http://<name>.localhost:<port>too: Origin checks on mutations, CORS allow-lists, CSRF trusted origins (DjangoCSRF_TRUSTED_ORIGINS), OAuth redirect URIs, cookieDomainattributes. Prefer one exported constant for the name over repeating the string.
- Custom middleware: grep for
- Test the guard in the project's own test style: the name passes, a sibling
(
other.localhost:<port>) is refused, and if mutations check Origin, the same pair for the origin. Run the full suite and the typecheck. - Make it the address the project prints: startup log, any
--open, README, CLAUDE.md or other agent instructions, service-file comments. Mention that the old addresses still work. - Restart and verify. A long-running service (launchd
KeepAlive, systemd, pm2) keeps the old code until restarted; check it isn't mid-request or mid-job first.
Expect 200, 200, and a refusal.for u in http://<name>.localhost:<port>/ http://127.0.0.1:<port>/ http://other.localhost:<port>/; do curl -s -o /dev/null -w "$u %{http_code}\n" "$u" done
Alongside Tailscale
The local name and tailscale serve don't interact. Serve proxies
https://<machine>.<tailnet>.ts.net:<port> to http://127.0.0.1:<port> and passes
the ts.net name as Host, so the guard keeps allowing that name as well, ideally only
when serve's Tailscale-User-Login header is the expected login.
- On macOS the CLI is often not on PATH:
/Applications/Tailscale.app/Contents/MacOS/Tailscale serve statusshows which ports are taken. The barehttps://<machine>.<tailnet>.ts.net(443) fits one app. - New app on the tailnet:
tailscale serve --bg --https=<port> http://127.0.0.1:<port>. Neverfunnel, which makes it public. - The tailnet URL also answers on the machine itself, so one synced bookmark can
cover every device. The
.localhostURL is the one that works with Tailscale off.
Pitfalls
- Browser storage is per origin. localStorage, cookies, IndexedDB, service workers and saved logins start empty at the new address. Say so; server-side state is untouched.
- IPv6 first. The name resolves to
::1as well as127.0.0.1. Browsers and curl fall back between them, but a strict client may not; if something fails where curl works, comparecurl -4andcurl -6. - Don't bind
0.0.0.0to "make it work". The name changes nothing about who can reach the server, and it shouldn't.
Report
Lead with the URL to bookmark. Then what still works, what resets (browser storage), the verification codes, and the commit if one was made.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

