Skip to content

feat(skills): add tailnet access for local servers - #22

Open
Quidge wants to merge 11 commits into
mainfrom
fm/tailscale-serve-skill
Open

feat(skills): add tailnet access for local servers#22
Quidge wants to merge 11 commits into
mainfrom
fm/tailscale-serve-skill

Conversation

@Quidge

@Quidge Quidge commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Intent

Follow-up commit on the same branch, decided by the user after reviewing PR #22: remove entirely the tailscale-serve cross-reference that an earlier commit on this branch added to .agents/skills/lavish/SKILL.md, restoring that file to its upstream content so the branch carries NO local edit to the vendored lavish skill.

Rationale the user chose: tailscale-serve stands on its own description, so an agent that needs a loopback URL to open on another device reaches it directly without lavish pointing at it. Removing the edit also keeps the vendored lavish skill free of a local deviation, which means a future re-vendor has nothing to reconcile and no ADAPTATION.md provenance file is needed. The user explicitly chose this over keeping the pointer or formalizing lavish as an adapted skill, so do NOT suggest re-adding a cross-reference from lavish, and do NOT suggest adding ADAPTATION.md or vendoring metadata.

This commit touches only .agents/skills/lavish/SKILL.md, and that file is now byte-identical to origin/main. Everything else on the branch is deliberately unchanged and still stands: the new .agents/skills/tailscale-serve/SKILL.md, the earlier sweep putting every Python invocation through 'uv run python', the docs/documentation-audiences.json entry classifying the new skill as agent-runtime, and the README 'Two-tier skill layout' line listing tailscale-serve as a public tool skill. Closes #11 as before.

Out of scope and must not be touched: the box-level install into ~/.agents/skills and ~/.claude/skills, which is pending a separate user decision, so nothing under $HOME may be modified.

Standing caveat already recorded and accepted on this branch, DO NOT re-flag as an oversight: 'uv run' resolves and syncs the surrounding project's environment before running, so inside a directory containing a pyproject.toml the hostname probe becomes coupled to that project's dependency resolution and fails when resolution fails, even though it only parses JSON from stdin. I verified this on this machine and raised it with the user with reproduction evidence; they reaffirmed plain 'uv run python' as the instructed form, and whether to add --no-project is a separate pending decision.

Verification for this commit: bin/fm-doc-audience-check.sh passes and its local link count dropped from 216 to 215, confirming the cross-reference link is gone; bin/fm-lint.sh passes; and 'git diff origin/main...HEAD' for .agents/skills/lavish/SKILL.md is empty, confirming the revert is exact. Documentation-only change, no executable code.

What Changed

  • Add a tailscale-serve skill for safely exposing loopback services to a private tailnet, including certificate detection, endpoint checks, verification, host allowlists, and targeted teardown.
  • Document tailscale-serve as a public tool skill available through the agent skill layout.
  • Classify the new skill as agent-runtime documentation.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed
✅ **Rebase** - passed
✅ **Review** - completed
✅ **Test** - passed
✅ **Document** - passed
✅ **Lint** - passed
✅ **Push** - passed

Quidge added 9 commits August 7, 2026 21:30
Adds `.agents/skills/tailscale-serve/SKILL.md`, a non-internal skill for
publishing a loopback-bound local server onto the operator's own tailnet
over plain HTTP and tearing it down again.

The recipe and its failure modes were verified live against tailscale
1.98.10 rather than taken from docs alone:

- `--https` is serve's default mode. On a tailnet with no HTTPS-certificate
  feature, `serve` blocks on certificate provisioning and prints nothing at
  all, so it reads as a hung command. The skill leads with a `CertDomains`
  preflight and `--http`, which is what makes this the common path.
- `serve` forwards the original `Host` header including the port, so any app
  with a host allowlist answers 403 until the tailnet name is added. This is
  the most common reason an otherwise-correct serve fails to load.
- `off` matches on the flags originally passed, not the target, and serve
  config persists across shell exit and Tailscale restart, so teardown is a
  required closing step rather than optional cleanup.

Safety rules are encoded as stated in the issue: serve rather than funnel,
keep the server on loopback, session-scoped only, and look before `reset`.

The skill carries no `metadata.internal`, so installers discover it, matching
how `.agents/skills/lavish` is packaged. No harness-specific skill mirrors are
added: the tracked `.claude/skills` symlink covers claude, and codex, cursor,
pi, and opencode read `.agents/skills/` natively.

Also points the lavish skill at it, since a lavish review page is loopback-only
and unreachable from a phone, and registers the new surface in the
documentation-audiences inventory.

Closes #11
… matters

Applies the writing-for-agents tenets the skill was meant to be authored to.
Same scope and same content boundaries; this is a legibility pass, not new
material, and every prior pipeline fix is preserved.

- Lead with the certificate probe. The silent `--https` hang is the failure
  that actually costs an agent time, so the one-second `CertDomains` check and
  the `--http` conclusion now open the skill instead of sitting mid-preflight.
- Split the old preflight into two named steps, so guarding an occupied
  endpoint is a structural step rather than a sentence buried under another
  heading.
- Drop the Symptoms table. Every row restated something the body already said,
  which is duplication the body and the table would drift apart on. Each row's
  content now lives once, at the point of use: the hang in step 1, the sudo
  error in step 2, the tailnet-membership check in step 4, and the 403 in its
  own section.
- Sharpen the description so it leads with the reaching-it-from-another-device
  trigger rather than the mechanism.

Net 76 deletions against 44 insertions.
@Quidge Quidge changed the title feat(skills): add tailnet serving guidance feat(skills): add tailnet access for loopback services Aug 7, 2026
Comment thread .agents/skills/tailscale-serve/SKILL.md Outdated
Comment on lines +18 to +19
read -r HOST CERTS < <(tailscale status --json | python3 -c "import json,sys;d=json.load(sys.stdin);print(d['Self']['DNSName'].rstrip('.'), len(d.get('CertDomains') or []))")
echo "host=$HOST certs=$CERTS"

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be uv run python ... instead of python3.

Comment thread .agents/skills/lavish/SKILL.md Outdated
Comment on lines +35 to +36
Lavish binds to loopback, so its URL does not reach a reviewer on a phone or another machine.
When that is where the review has to happen, follow the [`tailscale-serve` skill](../tailscale-serve/SKILL.md) to publish the port to your own tailnet, and tear that down when the review ends.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lavish skill isn't vendored, right? Do we need to vendor it if we're making updates to it?

Comment thread .agents/skills/tailscale-serve/SKILL.md Outdated
If you need to confirm it, this names the cause outright:

```bash
read -r HOST _ < <(tailscale status --json | python3 -c "import json,sys;d=json.load(sys.stdin);print(d['Self']['DNSName'].rstrip('.'), len(d.get('CertDomains') or []))")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here. All of the python3 should be replaced with uv run python ...

Sweeps every Python invocation in the skill onto `uv run python`, matching
the house convention for running Python on this fleet.

- The four hostname/certificate probe snippets now pipe into
  `uv run python -c` instead of `python3 -c`.
- The Django worked example now runs `uv run python manage.py runserver`,
  which is also how Django is actually driven on this fleet.

All four probe snippets were executed verbatim after the change and produce
the documented output.

Known caveat, deliberate: `uv run` resolves and syncs the surrounding
project's environment before running. Outside a Python project the probe is
instant, but inside a directory with a `pyproject.toml` the probe becomes
coupled to that project's dependency resolution and fails if resolution
fails, even though it only parses JSON. `uv run --no-project python` would
avoid that coupling while keeping the same convention; that choice is
pending and intentionally not made here.

This commit touches only the skill. The lavish pointer is deliberately left
alone pending a separate decision.
@Quidge Quidge changed the title feat(skills): add tailnet access for loopback services feat(skills): add Tailscale serve workflow Aug 7, 2026
Restores `.agents/skills/lavish/SKILL.md` to its upstream content, so this
branch carries no local edit to the vendored lavish skill at all.

`tailscale-serve` stands on its own description: an agent that needs a
loopback URL to open on another device reaches it directly, without lavish
having to point at it. Removing the edit also keeps the vendored skill free
of a local deviation, so a future re-vendor has nothing to reconcile and no
ADAPTATION.md provenance is needed.

The rest of the branch is unchanged: the skill itself, the uv-run sweep, the
documentation-audiences entry, and the README placement note all stand.
@Quidge Quidge changed the title feat(skills): add Tailscale serve workflow feat(skills): add tailnet access for local servers Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Skill: serve lavish and other loopback-bound local servers over Tailscale (session-scoped, tailnet-only)

1 participant