◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Cowork
◈ Cowork/2026-07-17Advanced

It Worked on My Machine, but Nobody Could Trigger It — Four Assumptions I Stripped Out of a Cowork Plugin

I bundled four working automation skills into a plugin and shared it. Only one of them ever fired. Here is how I measured skill trigger rate, and the four assumptions — vocabulary, paths, connections, and naming — I had to strip out before it was portable.

cowork16plugin3skillmcp20automation109distribution

✦ Premium Article

Working as an indie developer, I had accumulated four automation skills over six months, written a piece at a time between other work. All of them ran beautifully on my machine. So I packaged them into a plugin and handed it to a friend doing similar work.

His reply, a few days later, was short. "Installed it. Nothing happens."

Works here. Doesn't work there. An old story — but peeling the layers off one at a time showed me exactly what I had been quietly assuming. This is that record.

Break "it doesn't work" into stages

"It doesn't work" covers at least three different failures.

StageSymptomHow you spot it
Never firesClaude answers normally, skill is never invokedNo Skill tool call appears in the log
Fires, then diesInvoked, then stops on an errorPath, token, or permission errors surface
Finishes, wrong outputRuns to completion, result is offEvery step logs success; only the artifact is wrong

What was happening on his machine was the first one. If you skip past that and start debugging the body of the skill, you will dig in the wrong place for a long time.

I did exactly that. I suspected the connectors, then the permissions, and burned about an hour. The skill had never been called once, so nothing inside it mattered. When you are the only tester on your own tools, getting the order of suspicion wrong costs you the whole afternoon.

Turn trigger rate into a number

Arguing from intuition goes nowhere. "The description is probably bad" gives you no way to tell whether you fixed it.

So I wrote a small harness: replay a list of plausible utterances, record which skill gets picked. It runs claude -p headless and pulls the skill name out of the tool calls.

Test cases go in a JSONL file, one per line.

{"utterance": "pull together last week's sales into a report", "expect": "weekly-report"}
{"utterance": "generate the weekly report", "expect": "weekly-report"}
{"utterance": "can I get a summary for this week", "expect": "weekly-report"}
{"utterance": "sort through the issues that piled up", "expect": "issue-triage"}
{"utterance": "label the new issues that came in", "expect": "issue-triage"}
{"utterance": "draft the release notes", "expect": "release-notes"}
{"utterance": "check today's billing", "expect": "billing-check"}

expect is the skill you want chosen for that phrasing. Mix in the same intent said several different ways, not just the way you happen to say it. That matters more than it looks.

Here is the runner.

#!/usr/bin/env bash
# skill-trigger-check.sh — record which skill gets picked for each sample utterance
# usage: ./skill-trigger-check.sh cases.jsonl trigger-report.tsv
set -euo pipefail
 
CASES="${1:?usage: skill-trigger-check.sh <cases.jsonl> [out.tsv]}"
OUT="${2:-trigger-report.tsv}"
 
: > "$OUT"
 
while IFS= read -r line; do
  [ -z "$line" ] && continue
  utterance=$(jq -r '.utterance' <<<"$line")
  expected=$(jq -r '.expect'     <<<"$line")
 
  # One turn only. We want to know whether it fires, not to let it
  # actually relabel a real issue every time we test.
  actual=$(claude -p "$utterance" \
             --output-format stream-json --verbose --max-turns 1 2>/dev/null \
           | jq -rs '[ .[]
                       | select(.type == "assistant")
                       | .message.content[]?
                       | select(.type == "tool_use" and .name == "Skill")
                       | .input.skill ] | first // "none"')
 
  if [ "$actual" = "$expected" ]; then status="MATCH"; else status="MISS"; fi
  printf '%s\t%s\t%s\t%s\n' "$status" "$expected" "$actual" "$utterance" >> "$OUT"
done < "$CASES"
 
awk -F'\t' '
  { n++; if ($1 == "MATCH") m++ }
  END { printf "trigger rate: %d/%d (%.0f%%)\n", m, n, (n ? m*100/n : 0) }
' "$OUT"

--max-turns 1 keeps the test from doing real work. All I want to know is whether the skill gets picked, so I stop the moment it is. jq -rs collects the stream into an array, grabs the first Skill call, and falls back to none when there isn't one.

On my machine: 18 of 20 matched. 90%. No wonder it felt smooth.

On his machine, same cases: 5 of 20. 25%. Of four skills, exactly one was responding at all.

The moment there was a number, the conversation got concrete. Fifteen MISS rows tell you plainly what is going on.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A headless `claude -p` harness that replays 20 sample utterances and records which skill was picked — tracking the move from 25% to 85%
✦Three rules for rewriting a SKILL.md description from prose into routing input, with a concrete before and after
✦A bash preflight script, CI-ready as-is, that catches absolute paths, raw credentials, undocumented env vars, and collision-prone skill names
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ Cowork2026-08-23
Not Every Unreachable Connector Means the Same Thing in an Unattended Run
When an unattended task cannot reach a connector, the cause splits into three states: still connecting, unauthenticated, or genuinely absent. Each demands the opposite response. Here is the resolver that tells them apart.
◈ Cowork2026-05-04
Why Cowork Scheduled Tasks Stop Mid-Run and How to Recover
Diagnose Cowork scheduled task failures using measured exit codes and real error output — why safe.directory delays failure instead of fixing it, how to pick a writable root before cloning, and how to structure logs with reason codes.
◈ Cowork2026-09-08
Until I planted a failing sample, my unattended checks had never once failed
A check running on an unattended schedule lost its exit code to a single pipe added for readable logs. I measured where the status disappears and built a small harness that checks the checker.