feat(skills): add tailnet access for local servers - #22
Open
Quidge wants to merge 11 commits into
Open
Conversation
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
commented
Aug 7, 2026
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" |
Owner
Author
There was a problem hiding this comment.
This should be uv run python ... instead of python3.
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. |
Owner
Author
There was a problem hiding this comment.
This lavish skill isn't vendored, right? Do we need to vendor it if we're making updates to it?
| 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 []))") |
Owner
Author
There was a problem hiding this comment.
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
tailscale-serveskill for safely exposing loopback services to a private tailnet, including certificate detection, endpoint checks, verification, host allowlists, and targeted teardown.tailscale-serveas a public tool skill available through the agent skill layout.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ **Rebase** - passed
✅ **Review** - completed
✅ **Test** - passed
✅ **Document** - passed
✅ **Lint** - passed
✅ **Push** - passed