Atlas expedition 5 angles ↓

Testing & regression-checking Claude Code agent extensions before you ship them

How to prove the skill, subagent, hook, plugin, or MCP server you authored actually works before your team installs it — and where, in 2026, the tooling still runs out.

5 succeeded #222

TL;DR. Extension testability is a gradient, not a checkbox. Hooks are plain scripts with a fixed stdin/exit-code/JSON contract, so they take fully-deterministic CI (bats/pytest) [1]. Skills and subagents are model-in-the-loop: their description field is the unit under test, and you can only gate on hit-rate over a prompt set, never a single green run [2]. MCP servers and plugins add a third axis you don’t author — a supply chain that can rug-pull after install [3]. Nobody has a clean, purpose-built harness yet; the working stack is improvised from claude -p, Promptfoo, snapshot tests, and JSON-schema linters.

The description is the test, for both skills and subagents. Claude decides whether to fire a skill or delegate to a subagent by fuzzy-matching its description against the prompt [4] [5]. So “does my skill trigger?” and “does the parent route to my subagent?” are the same test, written the same way: positive prompts that must hit, plus near-miss prompts that must route to a sibling instead. Promptfoo expresses this directly with skill-used / not-skill-used assertions [2]; Anthropic’s skill-creator upgrade (shipped 3 March 2026) treats triggering as a false-positive/false-negative classifier and auto-tunes the description against sample prompts [6]. Both fight the same enemy: autonomous triggering is only ~50% reliable, so for must-fire workflows teams fall back to a deterministic UserPromptSubmit hook that calls Use Skill() explicitly [7].

Two different regression problems hide under one word. Functional drift is your own artifact changing — caught by snapshotting tools/list and failing CI on schema diff (Bellwether [8], the official MCP conformance suite [9], or FastMCP in-memory client tests [10]). A rug-pull is a dependency changing after you approved it — postmark-mcp behaved identically for 15 versions, then shipped a BCC backdoor in v1.0.16 [11]. These need opposite defenses: hash-pinning tool descriptions and alerting on change (mcp-scan / Snyk agent-scan [12]), pinning servers to a sha256 digest [13], and provenance — though provenance is not a silver bullet, since 633 malicious npm versions passed Sigstore verification with stolen certificates in May 2026 [14]. MCP still has no native re-approval when a tool mutates [3].

The realistic per-PR floor is deterministic, and cheap. Before any model call: claude plugin validate --strict (warnings become errors) [15], JSON-schema validation of plugin.json/marketplace.json (now official on SchemaStore [16]), a frontmatter/SKILL.md linter, and a “does it load” smoke test via claude -p --output-format stream-json reading the system/init event’s plugin_errors [17]. The CI-native linters that exist are tiny and single-maintainer — pulser [18], skill-validator [19] — which is itself the finding: this is emerging, and the consolidation signal is schemas landing on SchemaStore, not a dominant tool.

The surface itself is unstable. 2026 broke working assertions: the TaskAgent tool rename means transcript matchers must accept both strings, and regression #17591 (open since 12 Jan 2026) has TaskOutput returning raw JSONL instead of the subagent summary, breaking transcript-based dispatch checks [20]. The open question a small team can’t escape: deterministic linting and unit-tested hooks fit neatly per-PR, but the rug-pull surface has no per-PR answer — the malicious change lands after install, in someone else’s repo, long after your diff was green.

Sub-topics