Every MCP call from Claude Code was going out through claude.ai's connector infrastructure, across the public internet, through Cloudflare and NPM on piguard, back into koala's k3s ingress — to reach a service on the same machine the session is running on. For brain that means 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 actual user-facing symptom. Measured from koala: brain koala:30330 200 in 6.8ms infra koala:30342 200 in 3.6ms brain public edge 200 in 27.7ms infra starts working again as a side effect: its only fault was an SNI/cert gap at the public edge (TLS "unrecognized name"), which the NodePort path does not traverse. No NPM change needed for local use. Note this is not a relocation of existing config. The mcpServers block that lived in ~/.claude/settings.json was inert — Claude Code reads server definitions from ~/.claude.json, project .mcp.json, plugins and connectors, not from settings.json. Verified: a uniquely-named server placed there is invisible to both `claude mcp list` and `claude mcp get`. So before the plugin existed, koala had no direct path at all and every call went via Cloudflare by construction. That also explains how a stale GITEA_MCP_TOKEN went unnoticed for five days: nothing consumed it. Safe to land now because koala is the only host with the plugin installed (#24). The koala firewall must allow 30330/30342 on the Tailscale interface — NOT on the LAN broadly, so tailnet membership stays the perimeter — before the Macs get the plugin. Tracked as a prerequisite in #24. Measured today: those ports are refused from iguana over both LAN and tailnet while :22 answers, so the rule does not exist yet. Bump-Type: minor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
skills
Engineering skills library — markdown SKILL.md files that plug into any
AI-coding harness.
For Claude Code this repo is a plugin, installed from the marketplace it
also hosts; that path carries the hooks and MCP servers too. For Crush, Pi
and OpenCode it is a plain library wired by install.sh as one directory
symlink per harness. See SKILLS_INDEX.md.
What is a skill?
A skill is a single directory containing one or more markdown files. The
canonical entrypoint is <name>/SKILL.md. Additional reference material
lives alongside in the same directory (e.g.
clean-code/references/code-smells.md). Harnesses load skills on
demand — keep SKILL.md short and triggerable; push depth into
references.
See SKILLS_INDEX.md for the full catalogue with
descriptions and "use when" triggers.
Install
Claude Code — the plugin
/plugin marketplace add https://git.d-ma.be/mathias/skills.git
/plugin install skills@mathias
The plugin ships skills, the secret-guard + pre-task-ritual hooks, and the
brain/gitea/infra MCP servers as one versioned unit. Skills are namespaced
(skills:tdd). Updates land when .claude-plugin/plugin.json's version
moves — CI bumps it on every release.
Crush, Pi, OpenCode — install.sh
curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash
Idempotent. It clones or fast-forwards the checkout, then creates three symlinks — one directory link per harness, never one per skill:
| Link | Target |
|---|---|
~/.agents/skills |
$SKILLS_CHECKOUT_DIR/skills (Pi reads this path directly) |
~/.config/crush/skills |
~/.agents/skills |
~/.config/opencode/skills |
~/.agents/skills |
Adding a skill wires it everywhere with no re-run.
Where the checkout must live
SKILLS_CHECKOUT_DIR defaults to ~/.local/share/skills, and that default is
load-bearing: it is host-local, outside any synced or shared directory.
On this fleet ~/dev is an iCloud folder shared by flamingo and iguana, and it
spans two path roots — /Users/mathias on the Macs, /home/mathias on koala.
A checkout there is a git working tree with two concurrent writers, and any
absolute symlink stored there is broken on one side by construction. That is
what produced 243 dangling links across 12 repos (#23). Do not "fix" the
default to point into ~/dev.
Per-host env-var overrides
| Env var | Default |
|---|---|
SKILLS_REPO_URL |
https://git.d-ma.be/mathias/skills.git |
SKILLS_REF |
main (set to e.g. v0.4.0 to pin a release) |
SKILLS_CHECKOUT_DIR |
$HOME/.local/share/skills |
SKILLS_SKIP_FETCH |
unset (1 wires an existing checkout without touching HEAD) |
AGENTS_SKILLS_DIR |
$HOME/.agents/skills |
CRUSH_SKILLS_DIR |
$HOME/.config/crush/skills |
OPENCODE_SKILLS_DIR |
$HOME/.config/opencode/skills |
Antigravity and Mistral Vibe targets were removed — unused.
Checking a host
task doctor # or bin/doctor
Reports dangling links, links resolving into a foreign checkout, skill-count mismatches, stray entries, and leftover per-skill Claude symlinks. Non-zero exit on drift. It also runs at session start via the plugin, so a drifting host says so rather than waiting to be discovered.
Versioning
Every commit to main is auto-tagged as a semver release by the
.gitea/workflows/release.yml action. Bump direction follows the commit
footer:
Bump-Type: major→X.0.0Bump-Type: minor→0.Y.0- anything else →
0.0.Z(patch, the default)
Consumer projects can pin a version by setting SKILLS_REF=v0.1.0
before task skills:install; default is main for always-current.
Contributing a skill
- Add a directory
<short-kebab-name>/. - Create
<short-kebab-name>/SKILL.md— start with one-paragraph purpose, then triggers ("use when …"), then mechanics. - Add a row to
SKILLS_INDEX.md(description + use-when). - Commit directly to
main. Conventional-commit prefix (feat:/fix:/docs:). AddBump-Type: minorfooter when adding a new skill or expanding scope; default patch covers edits.
This repo is markdown-only and public — never include secrets, client names, or anything under NDA in a skill.