Roll the plugin out to flamingo and iguana — both currently run with no skills and no secret-guard #24

Closed
opened 2026-08-23 12:41:50 +00:00 by mathias · 4 comments
Owner

koala is migrated to the plugin (v0.4.1). Both Macs are not, and their current state is worse than "not migrated".

What the audit found on the Macs

~/dev is a single iCloud folder shared by flamingo and iguana, so they share one copy of it. Their host-local config is separate per machine.

  • ~/.claude/skills on iguana holds only gsd-* directories. Zero engineering skills.
  • No ~/.config/crush/skills, no ~/.agents/skills, no ~/.local/share/skills checkout.
  • ~/dev/.skills still exists there because the shared local-dev checkout is pinned at e6d3c2d, pre-extraction. A git pull in ~/dev deletes it with nothing in its place.
  • Neither Mac has secret-guard.sh or pre-task-ritual.sh. Those lived only in koala's ~/.claude/hooks/.

The last point is the reason this is not cosmetic. secret-guard is the control that blocks the decrypt-and-print shape behind the 2026-08-05 ntfy-token leak, the op item get shape behind 2026-07-30, and the base64-masking bypass behind 2026-06-11. Two of three machines have been running without it.

Steps per Mac

/plugin marketplace add https://git.d-ma.be/mathias/skills.git
/plugin install skills@mathias
curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash   # Crush/Pi/OpenCode

Then remove any leftover ~/.claude/skills/<name> symlinks (plugin skills do not override same-named user skills — leftovers load everything twice; bin/doctor reports this), and confirm with task doctor.

Must be done per machine: ~/.claude and ~/.local/share are host-local, not in the shared folder. The plugin install cannot be done once and shared.

Blockers

  • flamingo is unreachable: ssh port 22 refused from koala (Remote Login off). Needs enabling, or the steps run by hand at the keyboard.
  • Claude Code version unknown on both. koala is on 2.1.222 and the source: "." marketplace layout validated there. Anything older than v2.1.196 skips plugins at the marketplace root and the install will silently produce no skills — check claude --version first.

Also worth doing while there

~/dev on the Macs is a git working tree inside iCloud with two machines writing to it. Every repo in it has a gitea remote, so the remote is already the sync and iCloud adds only concurrent-writer hazards, .icloud placeholders, and the cross-root symlink breakage that caused #23. Moving ~/dev out of iCloud is tracked separately.

koala is migrated to the plugin (v0.4.1). Both Macs are not, and their current state is worse than "not migrated". ## What the audit found on the Macs `~/dev` is a single iCloud folder shared by flamingo and iguana, so they share one copy of it. Their host-local config is separate per machine. - `~/.claude/skills` on iguana holds **only** `gsd-*` directories. Zero engineering skills. - No `~/.config/crush/skills`, no `~/.agents/skills`, no `~/.local/share/skills` checkout. - `~/dev/.skills` still exists there because the shared `local-dev` checkout is pinned at `e6d3c2d`, **pre-extraction**. A `git pull` in `~/dev` deletes it with nothing in its place. - Neither Mac has `secret-guard.sh` or `pre-task-ritual.sh`. Those lived only in koala's `~/.claude/hooks/`. The last point is the reason this is not cosmetic. `secret-guard` is the control that blocks the decrypt-and-print shape behind the 2026-08-05 ntfy-token leak, the `op item get` shape behind 2026-07-30, and the base64-masking bypass behind 2026-06-11. Two of three machines have been running without it. ## Steps per Mac ``` /plugin marketplace add https://git.d-ma.be/mathias/skills.git /plugin install skills@mathias curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash # Crush/Pi/OpenCode ``` Then remove any leftover `~/.claude/skills/<name>` symlinks (plugin skills do not override same-named user skills — leftovers load everything twice; `bin/doctor` reports this), and confirm with `task doctor`. Must be done **per machine**: `~/.claude` and `~/.local/share` are host-local, not in the shared folder. The plugin install cannot be done once and shared. ## Blockers - **flamingo is unreachable**: ssh port 22 refused from koala (Remote Login off). Needs enabling, or the steps run by hand at the keyboard. - **Claude Code version unknown on both.** koala is on 2.1.222 and the `source: "."` marketplace layout validated there. Anything older than v2.1.196 skips plugins at the marketplace root and the install will silently produce no skills — check `claude --version` first. ## Also worth doing while there `~/dev` on the Macs is a git working tree inside iCloud with two machines writing to it. Every repo in it has a gitea remote, so the remote is already the sync and iCloud adds only concurrent-writer hazards, `.icloud` placeholders, and the cross-root symlink breakage that caused #23. Moving `~/dev` out of iCloud is tracked separately.
Author
Owner

Prerequisite added: koala firewall must allow the MCP NodePorts on the Tailscale interface

The plugin's .mcp.json now points at tailnet-direct NodePorts instead of the public d-ma.be hostnames (perf(mcp): reach brain and infra over tailnet NodePorts, v0.4.3):

brain  http://koala:30330/mcp
infra  http://koala:30342/mcp

This must be resolved before either Mac installs the plugin, or they will get a working skills install with dead MCP servers.

Why the change

Every MCP call was going out through claude.ai's connector infrastructure → public internet → Cloudflare → NPM on piguard → koala k3s ingress, to reach a service on the machine the session is already running on. For brain that is a two-cloud round trip to read /var/lib/supervisor/brain, a file on koala's local disk. Cloudflare rate-limits and size-caps tool results on that path, which is the symptom that prompted this.

Measured from koala:

Path Result
brain via NodePort koala:30330 200 in 6.8 ms
infra via NodePort koala:30342 200 in 3.6 ms
brain via public edge 200 in 27.7 ms

infra also starts working again as a side effect — its only fault was an SNI/cert gap at the public edge, which the NodePort path never traverses.

The blocker, measured

From iguana (10.0.1.215, same /24 as koala at 10.0.1.20):

Target Result
koala:22 open
koala:30330 by name refused in 3 ms
10.0.1.20:30330 direct LAN refused
100.109.2.126:30330 over Tailscale refused

curl: (7) Failed to connect ... after 3 ms — fast refusal, not a timeout, and SSH to the same host answers. So this is not routing or VLAN segmentation; koala refuses these ports to everything except localhost. Being on the homelab LAN buys nothing today.

The ask

Allow 30330, 30340, 30342 inbound on the Tailscale interface only — not on 10.0.1.0/24.

Scoping to tailscale0 rather than the LAN is the point: it keeps tailnet membership as the perimeter, consistent with knowledge/homelab-network-perimeter-model. A LAN-scoped rule would grant access to anything that joins the home network and would still leave the Macs broken when they are away from home. Tailscale already negotiates a direct path between these hosts, so there is no latency cost.

30340 (gitea-mcp) is included for when that server returns to .mcp.json — it is currently removed pending the token question below.

This is infra, so it belongs with the infra agent rather than here.

Also still open for the Macs

  • Claude Code version on both is unknown. koala is 2.1.222 and the source: "." marketplace layout validated there; anything older than v2.1.196 skips plugins at the marketplace root and silently installs zero skills. Check claude --version before installing.
  • flamingo ssh is refused (Remote Login off), so it cannot be done remotely.
  • gitea MCP is absent from the plugin pending a decision: koala's ~/.op-env points at op://HomeLab/GITEA_MCP_TOKEN, an item orphaned by the 2026-07-30 consolidation onto gitea-mcp-shared-token in the AgentSecrets vault — which koala's 1Password service account cannot read. Either grant that access, or leave hosts without a gitea token and rely on the claude.ai connector.
## Prerequisite added: koala firewall must allow the MCP NodePorts on the Tailscale interface The plugin's `.mcp.json` now points at tailnet-direct NodePorts instead of the public `d-ma.be` hostnames (`perf(mcp): reach brain and infra over tailnet NodePorts`, v0.4.3): ``` brain http://koala:30330/mcp infra http://koala:30342/mcp ``` **This must be resolved before either Mac installs the plugin, or they will get a working skills install with dead MCP servers.** ### Why the change Every MCP call was going out through claude.ai's connector infrastructure → public internet → Cloudflare → NPM on piguard → koala k3s ingress, to reach a service on the machine the session is already running on. For `brain` that is a two-cloud round trip to read `/var/lib/supervisor/brain`, a file on koala's local disk. Cloudflare rate-limits and size-caps tool results on that path, which is the symptom that prompted this. Measured from koala: | Path | Result | |---|---| | `brain` via NodePort `koala:30330` | 200 in **6.8 ms** | | `infra` via NodePort `koala:30342` | 200 in **3.6 ms** | | `brain` via public edge | 200 in 27.7 ms | `infra` also starts working again as a side effect — its only fault was an SNI/cert gap at the public edge, which the NodePort path never traverses. ### The blocker, measured From iguana (10.0.1.215, **same /24** as koala at 10.0.1.20): | Target | Result | |---|---| | `koala:22` | open | | `koala:30330` by name | **refused in 3 ms** | | `10.0.1.20:30330` direct LAN | **refused** | | `100.109.2.126:30330` over Tailscale | **refused** | `curl: (7) Failed to connect ... after 3 ms` — fast refusal, not a timeout, and SSH to the same host answers. So this is not routing or VLAN segmentation; koala refuses these ports to everything except localhost. Being on the homelab LAN buys nothing today. ### The ask Allow `30330`, `30340`, `30342` inbound **on the Tailscale interface only** — not on `10.0.1.0/24`. Scoping to `tailscale0` rather than the LAN is the point: it keeps tailnet membership as the perimeter, consistent with `knowledge/homelab-network-perimeter-model`. A LAN-scoped rule would grant access to anything that joins the home network and would still leave the Macs broken when they are away from home. Tailscale already negotiates a direct path between these hosts, so there is no latency cost. `30340` (gitea-mcp) is included for when that server returns to `.mcp.json` — it is currently removed pending the token question below. This is infra, so it belongs with the infra agent rather than here. ### Also still open for the Macs - **Claude Code version** on both is unknown. koala is 2.1.222 and the `source: "."` marketplace layout validated there; anything older than **v2.1.196** skips plugins at the marketplace root and silently installs zero skills. Check `claude --version` before installing. - **flamingo ssh is refused** (Remote Login off), so it cannot be done remotely. - **`gitea` MCP is absent from the plugin** pending a decision: koala's `~/.op-env` points at `op://HomeLab/GITEA_MCP_TOKEN`, an item orphaned by the 2026-07-30 consolidation onto `gitea-mcp-shared-token` in the **AgentSecrets** vault — which koala's 1Password service account cannot read. Either grant that access, or leave hosts without a gitea token and rely on the claude.ai connector.
Author
Owner

Correction: the firewall prerequisite above is wrong — do not apply it

My previous comment asked for an iptables rule opening the MCP NodePorts on tailscale0. Disregard it. Reading koala's actual chain showed the premise was false, and the rule would have punched a hole around a deliberate control while probably not even working.

What's actually blocking

-P INPUT ACCEPT
-A INPUT ... -j KUBE-ROUTER-INPUT
-A INPUT ... -j KUBE-EXTERNAL-SERVICES
-A INPUT -j ts-input

Default policy is ACCEPT and there is no host-level reject. Nothing is filtering at the firewall layer. The RST comes from kube-proxy's KUBE-EXTERNAL-SERVICES, acting on a NetworkPolicy:

# supervisor/allow-callers
from:
  - namespaceSelector: { kubernetes.io/metadata.name: traefik }
  - namespaceSelector: { kubernetes.io/metadata.name: monitoring }
ports: [ { port: 3300, protocol: TCP } ]

Plus default-deny-ingress in every namespace. NodePort access from another host isn't an oversight — Traefik is the only sanctioned ingress, by design. An INPUT ACCEPT rule sits upstream of where the deny happens and would not have helped.

One more constraint found: externalTrafficPolicy is unset on all three services, so it defaults to Cluster, which SNATs the source address. A NetworkPolicy ipBlock for the Tailscale CGNAT range therefore cannot match — the tailnet client IP is gone by the time the packet reaches the pod. Admitting tailnet clients to the NodePort would require flipping all three services to externalTrafficPolicy: Local first.

The prerequisite that replaces it: none, for brain and gitea

Traefik is a NodePort service in the traefik namespace (80:30081, 443:30444) — that's how NPM on piguard reaches it. Measured from iguana:

Target Result
10.0.1.20:30081 + Host: brain-mcp.d-ma.be 401 in 7.6 ms
100.109.2.126:30081 (tailnet) same header 401 in 6.0 ms

401 is the auth challenge — the request routed correctly through Traefik to the brain service. So the sanctioned path is already reachable from other hosts, over both LAN and tailnet, with no cluster changes at all. No NetworkPolicy edit, no firewall rule, no externalTrafficPolicy change, no certificates.

The only change needed is DNS: resolve brain-mcp.d-ma.be and git-mcp.d-ma.be to koala on the tailnet, so the name never leaves for Cloudflare. Then .mcp.json uses http://brain-mcp.d-ma.be:30081/mcp.

Plain HTTP is correct here, not a downgrade: koala's ingresses are port 80 with no tls: block and there is no cert-manager — TLS terminates at NPM and the koala hop has always been plaintext. On the tailnet that hop is encrypted by WireGuard anyway.

The one genuine gap: infra-mcp has no Ingress

kubectl -n infra-mcp get ingress returns nothing. It is NodePort-only (30342), so it cannot use the Traefik path. Either create an Ingress for it, or accept it stays koala-local. Not a blocker for the Mac rollout — it simply won't be available there until an Ingress exists.

Net effect on this issue

The Mac rollout is no longer blocked on an infra change. It needs a DNS entry and a .mcp.json update, both of which are reversible and neither of which touches the security posture.

Still open, unchanged: Claude Code version check on both Macs (anything below v2.1.196 silently installs zero skills), flamingo's ssh being off, and the gitea token / AgentSecrets question.

## Correction: the firewall prerequisite above is wrong — do not apply it My previous comment asked for an iptables rule opening the MCP NodePorts on `tailscale0`. **Disregard it.** Reading koala's actual chain showed the premise was false, and the rule would have punched a hole around a deliberate control while probably not even working. ### What's actually blocking ``` -P INPUT ACCEPT -A INPUT ... -j KUBE-ROUTER-INPUT -A INPUT ... -j KUBE-EXTERNAL-SERVICES -A INPUT -j ts-input ``` Default policy is **ACCEPT** and there is no host-level reject. Nothing is filtering at the firewall layer. The RST comes from kube-proxy's `KUBE-EXTERNAL-SERVICES`, acting on a NetworkPolicy: ```yaml # supervisor/allow-callers from: - namespaceSelector: { kubernetes.io/metadata.name: traefik } - namespaceSelector: { kubernetes.io/metadata.name: monitoring } ports: [ { port: 3300, protocol: TCP } ] ``` Plus `default-deny-ingress` in every namespace. NodePort access from another host isn't an oversight — **Traefik is the only sanctioned ingress, by design**. An `INPUT` ACCEPT rule sits upstream of where the deny happens and would not have helped. One more constraint found: `externalTrafficPolicy` is unset on all three services, so it defaults to `Cluster`, which SNATs the source address. A NetworkPolicy `ipBlock` for the Tailscale CGNAT range therefore **cannot match** — the tailnet client IP is gone by the time the packet reaches the pod. Admitting tailnet clients to the NodePort would require flipping all three services to `externalTrafficPolicy: Local` first. ### The prerequisite that replaces it: none, for brain and gitea Traefik is a NodePort service in the `traefik` namespace (`80:30081`, `443:30444`) — that's how NPM on piguard reaches it. Measured from iguana: | Target | Result | |---|---| | `10.0.1.20:30081` + `Host: brain-mcp.d-ma.be` | **401 in 7.6 ms** | | `100.109.2.126:30081` (tailnet) same header | **401 in 6.0 ms** | `401` is the auth challenge — the request routed correctly through Traefik to the brain service. So the sanctioned path is **already reachable from other hosts, over both LAN and tailnet, with no cluster changes at all.** No NetworkPolicy edit, no firewall rule, no `externalTrafficPolicy` change, no certificates. The only change needed is DNS: resolve `brain-mcp.d-ma.be` and `git-mcp.d-ma.be` to koala on the tailnet, so the name never leaves for Cloudflare. Then `.mcp.json` uses `http://brain-mcp.d-ma.be:30081/mcp`. Plain HTTP is correct here, not a downgrade: koala's ingresses are port 80 with no `tls:` block and there is no cert-manager — TLS terminates at NPM and the koala hop has always been plaintext. On the tailnet that hop is encrypted by WireGuard anyway. ### The one genuine gap: `infra-mcp` has no Ingress `kubectl -n infra-mcp get ingress` returns nothing. It is NodePort-only (`30342`), so it cannot use the Traefik path. Either create an Ingress for it, or accept it stays koala-local. Not a blocker for the Mac rollout — it simply won't be available there until an Ingress exists. ### Net effect on this issue The Mac rollout is **no longer blocked on an infra change**. It needs a DNS entry and a `.mcp.json` update, both of which are reversible and neither of which touches the security posture. Still open, unchanged: Claude Code version check on both Macs (anything below v2.1.196 silently installs zero skills), flamingo's ssh being off, and the `gitea` token / AgentSecrets question.
Author
Owner

Consolidated — supersedes both earlier prerequisite comments

I gave three different answers on this prerequisite as each test corrected the last. This comment replaces them. Ignore the iptables rule and the split-DNS record; neither is needed.

Resolved: MCP transport (shipped in v0.6.0)

brain  https://koala.tail30472e.ts.net:8443/mcp  ->  127.0.0.1:30330
infra  https://koala.tail30472e.ts.net:8444/mcp  ->  127.0.0.1:30342

Tailscale Serve on koala terminates TLS with a genuine cert and proxies over loopback, so requests reach the NodePort from koala itself — the case default-deny-ingress + allow-callers already permits. Verified ✔ Connected for both.

What this avoids, all of which I proposed at some point and all of which turned out unnecessary:

Proposed Why it's not needed
iptables -I INPUT ... ACCEPT on tailscale0 -P INPUT ACCEPT, no host filtering at all. The RST came from kube-proxy's KUBE-EXTERNAL-SERVICES enforcing a NetworkPolicy, upstream of where the rule would sit
externalTrafficPolicy: Local + ipBlock: 100.64.0.0/10 Only required to admit tailnet clients directly to the NodePort. Serve reaches it over loopback instead
Split-DNS record for brain-mcp.d-ma.be MagicDNS already provides the name
cert-manager / Traefik TLS Tailscale issues the cert

The NetworkPolicy posture is untouched. Traefik remains the only external ingress.

Remaining Mac prerequisite: accept Tailscale DNS

Serve resolves via MagicDNS, and matches on SNI — so the raw tailnet IP is not a substitute. On iguana:

dig @100.100.100.100 koala.tail30472e.ts.net  ->  100.109.2.126   ✓
dig koala.tail30472e.ts.net                   ->  (nothing)       ✗

Tailscale is connected there (utun0, 100.113.19.87) but is not installed as a DNS provider, so the LAN resolver answers and knows nothing about .ts.net. Per Mac:

tailscale set --accept-dns=true

or the equivalent "Use Tailscale DNS" toggle in the menu-bar app. Verify with dig koala.tail30472e.ts.net — a bare dig, not @100.100.100.100, which already works and proves nothing.

Do not enable "Override DNS servers" in the admin console to fix this: Global nameservers is empty there, so overriding would point clients at nothing.

Full Mac checklist

  1. claude --version — must be ≥ v2.1.196; older silently installs zero skills from a marketplace-root plugin
  2. tailscale set --accept-dns=true, verify with a bare dig
  3. /plugin marketplace add https://git.d-ma.be/mathias/skills.git then /plugin install skills@mathias
  4. curl -fsSL .../install.sh | bash for Crush/Pi/OpenCode
  5. Remove any leftover ~/.claude/skills/<name> symlinks — plugin skills do not override same-named user skills, so leftovers double-load. bin/doctor reports this
  6. task doctor → expect exit 0

flamingo still needs Remote Login enabled, or steps run at the keyboard.

Unchanged

gitea stays out of .mcp.json pending the AgentSecrets decision. infra-mcp has no Ingress, which no longer matters — Serve doesn't use one.

Durability caveat worth tracking

Serve config lives in tailscaled state on koala, not in git. If koala is rebuilt, the plugin's MCP endpoints break with no signal — the same silent-drift failure class this whole issue is about. Worth either capturing the two tailscale serve commands in the infra repo's provisioning or adding a doctor check that the endpoints answer.

## Consolidated — supersedes both earlier prerequisite comments I gave three different answers on this prerequisite as each test corrected the last. This comment replaces them. **Ignore the iptables rule and the split-DNS record; neither is needed.** ### Resolved: MCP transport (shipped in v0.6.0) ``` brain https://koala.tail30472e.ts.net:8443/mcp -> 127.0.0.1:30330 infra https://koala.tail30472e.ts.net:8444/mcp -> 127.0.0.1:30342 ``` Tailscale Serve on koala terminates TLS with a genuine cert and proxies over loopback, so requests reach the NodePort **from koala itself** — the case `default-deny-ingress` + `allow-callers` already permits. Verified `✔ Connected` for both. What this avoids, all of which I proposed at some point and all of which turned out unnecessary: | Proposed | Why it's not needed | |---|---| | `iptables -I INPUT ... ACCEPT` on `tailscale0` | `-P INPUT ACCEPT`, no host filtering at all. The RST came from kube-proxy's `KUBE-EXTERNAL-SERVICES` enforcing a NetworkPolicy, upstream of where the rule would sit | | `externalTrafficPolicy: Local` + `ipBlock: 100.64.0.0/10` | Only required to admit tailnet clients *directly* to the NodePort. Serve reaches it over loopback instead | | Split-DNS record for `brain-mcp.d-ma.be` | MagicDNS already provides the name | | cert-manager / Traefik TLS | Tailscale issues the cert | The NetworkPolicy posture is untouched. Traefik remains the only external ingress. ### Remaining Mac prerequisite: accept Tailscale DNS Serve resolves via MagicDNS, and matches on **SNI** — so the raw tailnet IP is not a substitute. On iguana: ``` dig @100.100.100.100 koala.tail30472e.ts.net -> 100.109.2.126 ✓ dig koala.tail30472e.ts.net -> (nothing) ✗ ``` Tailscale is connected there (`utun0`, `100.113.19.87`) but is not installed as a DNS provider, so the LAN resolver answers and knows nothing about `.ts.net`. Per Mac: ```sh tailscale set --accept-dns=true ``` or the equivalent "Use Tailscale DNS" toggle in the menu-bar app. Verify with `dig koala.tail30472e.ts.net` — a bare `dig`, not `@100.100.100.100`, which already works and proves nothing. **Do not** enable "Override DNS servers" in the admin console to fix this: Global nameservers is empty there, so overriding would point clients at nothing. ### Full Mac checklist 1. `claude --version` — **must be ≥ v2.1.196**; older silently installs zero skills from a marketplace-root plugin 2. `tailscale set --accept-dns=true`, verify with a bare `dig` 3. `/plugin marketplace add https://git.d-ma.be/mathias/skills.git` then `/plugin install skills@mathias` 4. `curl -fsSL .../install.sh | bash` for Crush/Pi/OpenCode 5. Remove any leftover `~/.claude/skills/<name>` symlinks — plugin skills do not override same-named user skills, so leftovers double-load. `bin/doctor` reports this 6. `task doctor` → expect exit 0 flamingo still needs Remote Login enabled, or steps run at the keyboard. ### Unchanged `gitea` stays out of `.mcp.json` pending the AgentSecrets decision. `infra-mcp` has no Ingress, which no longer matters — Serve doesn't use one. ### Durability caveat worth tracking Serve config lives in tailscaled state on koala, not in git. If koala is rebuilt, the plugin's MCP endpoints break with no signal — the same silent-drift failure class this whole issue is about. Worth either capturing the two `tailscale serve` commands in the infra repo's provisioning or adding a `doctor` check that the endpoints answer.
Author
Owner

Done — all three hosts installed

Host Claude Code Plugin install.sh doctor
koala 2.1.222 ✔ v0.6.0 3 links exit 0
flamingo 2.1.231 ✔ 3 links exit 0
iguana 2.1.218 ✔ 3 links exit 0

All three above the v2.1.196 floor, so no silent zero-skill installs.

What each host needed

flamingo — tailscale set --accept-dns=true applied cleanly (the CLI/daemon version warning is patch-level skew and harmless). Verified end to end: MagicDNS resolves and the Serve endpoint answers 401 with a valid cert, no -k.

Two pre-existing messes cleared:

  • 36 legacy symlinks in ~/.claude/skills pointing at /Users/mathias/dev/.skills/, with zero dangling. That directory still exists because ~/dev is the iCloud folder and its local-dev checkout is pinned pre-extraction at e6d3c2d. So they resolved fine and served the pre-extraction copy of the library — stale content, no error, silently shadowing the plugin. This is the silent-stale case from #23, still live, found only because bin/doctor looks for it.
  • ~/.config/crush/skills held real directories. install.sh refused to replace them, correctly. They turned out to be Anthropic's official skill set rather than stale copies — preserved at ~/.config/crush/skills.anthropic, and now tracked as #27, since the current one-link-per-harness design cannot serve two sources at once.

iguana — installed over ssh. Clean slate: no legacy symlinks, no pre-existing harness directories.

One outstanding item on iguana: MCP will not connect yet

CorpDNS: true is set, but MagicDNS names still do not resolve there and the Serve endpoint fails to connect. The running tailscaled is 1.98.8 against a 1.102.3 CLI — a major-version skew, and the daemon predates the DNS integration the setting relies on.

Skills are unaffected; only the MCP servers are. Fix is a Tailscale restart on iguana (GUI app restart, or sudo launchctl kickstart -k system/com.tailscale.tailscaled), then verify:

dscacheutil -q host -a name koala.tail30472e.ts.net     # expect 100.109.2.126
curl -sS -o /dev/null -w '%{http_code}\n' -X POST -d '{}' https://koala.tail30472e.ts.net:8443/mcp

401 is success. Note dig is not a valid test on macOS — it reads /etc/resolv.conf and ignores the scoped resolver Tailscale installs. Use dscacheutil or the curl.

Remaining, tracked elsewhere

  • #27 — multi-source support, and rescuing gotth-dev from flamingo's Crush directory into this repo. It is one machine, one harness, no git, no backup.
  • gitea MCP still absent pending the AgentSecrets vault decision.
  • Serve config lives in tailscaled state on koala, not in git — if koala is rebuilt the MCP endpoints break with no signal. Worth capturing in infra provisioning or adding a doctor check.

Closing.

## Done — all three hosts installed | Host | Claude Code | Plugin | install.sh | doctor | |---|---|---|---|---| | koala | 2.1.222 | ✔ v0.6.0 | 3 links | **exit 0** | | flamingo | 2.1.231 | ✔ | 3 links | **exit 0** | | iguana | 2.1.218 | ✔ | 3 links | **exit 0** | All three above the v2.1.196 floor, so no silent zero-skill installs. ### What each host needed **flamingo** — `tailscale set --accept-dns=true` applied cleanly (the CLI/daemon version warning is patch-level skew and harmless). Verified end to end: MagicDNS resolves and the Serve endpoint answers **401** with a valid cert, no `-k`. Two pre-existing messes cleared: - **36 legacy symlinks** in `~/.claude/skills` pointing at `/Users/mathias/dev/.skills/`, with **zero dangling**. That directory still exists because `~/dev` is the iCloud folder and its `local-dev` checkout is pinned pre-extraction at `e6d3c2d`. So they resolved fine and served the *pre-extraction* copy of the library — stale content, no error, silently shadowing the plugin. This is the silent-stale case from #23, still live, found only because `bin/doctor` looks for it. - `~/.config/crush/skills` held real directories. `install.sh` refused to replace them, correctly. They turned out to be Anthropic's official skill set rather than stale copies — preserved at `~/.config/crush/skills.anthropic`, and now tracked as **#27**, since the current one-link-per-harness design cannot serve two sources at once. **iguana** — installed over ssh. Clean slate: no legacy symlinks, no pre-existing harness directories. ### One outstanding item on iguana: MCP will not connect yet `CorpDNS: true` is set, but MagicDNS names still do not resolve there and the Serve endpoint fails to connect. The running **tailscaled is 1.98.8** against a 1.102.3 CLI — a major-version skew, and the daemon predates the DNS integration the setting relies on. Skills are unaffected; only the MCP servers are. Fix is a Tailscale restart on iguana (GUI app restart, or `sudo launchctl kickstart -k system/com.tailscale.tailscaled`), then verify: ```sh dscacheutil -q host -a name koala.tail30472e.ts.net # expect 100.109.2.126 curl -sS -o /dev/null -w '%{http_code}\n' -X POST -d '{}' https://koala.tail30472e.ts.net:8443/mcp ``` `401` is success. Note `dig` is **not** a valid test on macOS — it reads `/etc/resolv.conf` and ignores the scoped resolver Tailscale installs. Use `dscacheutil` or the curl. ### Remaining, tracked elsewhere - **#27** — multi-source support, and rescuing `gotth-dev` from flamingo's Crush directory into this repo. It is one machine, one harness, no git, no backup. - `gitea` MCP still absent pending the AgentSecrets vault decision. - Serve config lives in tailscaled state on koala, not in git — if koala is rebuilt the MCP endpoints break with no signal. Worth capturing in infra provisioning or adding a doctor check. Closing.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/skills#24