agentleFS
y-a-v-ay-a-v-a/skills/local-url/SKILL.md

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.

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
  1. Distinctive:

    No public DNS name can claim it, so admitting it to a Host guard does not reopen DNS rebinding.

    line 20
  2. Distinctive:

    Prefer one exported constant for the name over repeating the string.

    line 50
  3. Very specific:

    Never funnel, which makes it public.

    line 80
  4. Very specific:

    Don't bind 0.0.0.0 to "make it work".

    line 92

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 localhost and every name under it for loopback. Chrome and Firefox answer *.localhost themselves, 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

  1. 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.
  2. 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.
  3. Admit the exact name (not a *.localhost wildcard) wherever hosts are checked:
    • Custom middleware: grep for headers.host, allowedHosts, Host header.
    • Framework knobs: Vite server.allowedHosts, webpack-dev-server allowedHosts, Django ALLOWED_HOSTS, Rails config.hosts, Next.js allowedDevOrigins.
    • Anything keyed on origin needs http://<name>.localhost:<port> too: Origin checks on mutations, CORS allow-lists, CSRF trusted origins (Django CSRF_TRUSTED_ORIGINS), OAuth redirect URIs, cookie Domain attributes. Prefer one exported constant for the name over repeating the string.
  4. 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.
  5. 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.
  6. 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.
    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
    Expect 200, 200, and a refusal.

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 status shows which ports are taken. The bare https://<machine>.<tailnet>.ts.net (443) fits one app.
  • New app on the tailnet: tailscale serve --bg --https=<port> http://127.0.0.1:<port>. Never funnel, which makes it public.
  • The tailnet URL also answers on the machine itself, so one synced bookmark can cover every device. The .localhost URL 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 ::1 as well as 127.0.0.1. Browsers and curl fall back between them, but a strict client may not; if something fails where curl works, compare curl -4 and curl -6.
  • Don't bind 0.0.0.0 to "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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.