#1352·skills

skill-creator: run_eval.py parallel workers cross-match skill UUIDs, producing false-negative trigger rates

Author: liam-ai-realityCreated Jun 25, 2026Updated Sep 29, 2026

Summary

skills/skill-creator/scripts/run_eval.py produces systematically wrong (near-zero) trigger rates whenever --num-workers > 1 (the default is 10). The failure is silent: every query — including ones that obviously should trigger — comes back with trigger_rate ≈ 0, which then makes all should_trigger: false cases "pass" trivially and all should_trigger: true cases "fail". The aggregate looks like a bad skill description when in fact the measurement is broken.

Root cause

run_single_query writes each query's temp command file into a shared project commands dir and runs every claude -p in the same project:

python
clean_name = f"{skill_name}-skill-{unique_id}"          # line ~52  (per-query UUID)
project_commands_dir = Path(project_root) / ".claude" / "commands"   # line ~53  (SHARED)
command_file = project_commands_dir / f"{clean_name}.md"
...
cwd=project_root,                                        # line ~89  (SHARED)

Detection then requires the model's invoked skill name to contain this query's UUID:

python
if clean_name in accumulated_json:   # line ~147
    return True
...
return clean_name in accumulated_json   # line ~152

With ProcessPoolExecutor(max_workers=num_workers) (line ~198) running ~10 queries concurrently, up to ~10 command files — identical descriptions, different UUIDs — coexist in the shared .claude/commands/ dir. Each claude -p sees all of them and invokes one fleet of the skill, but the per-query check only matches its own UUID. The model almost always triggers a sibling worker's UUID → the query records a false negative. Result: trigger rate collapses to ~0 across the board.

Reproduction / evidence

Running the same 12-query trigger set against the same skill + description, only changing --num-workers:

Run --num-workers Result
Parallel (default) 12 0/12 queries triggered — even a query literally starting "Fleet tune the GET /api/board endpoint…" against a description containing the phrase fleet tune returned 0/3. All negatives "passed" only because nothing fired.
Serial 1 10/12 correct — clean signal: 4/6 positives fire, 6/6 negatives correctly stay off.

Manually invoking one obvious positive with a single command file present shows the model's first tool call is Skill(<the skill>) — i.e. triggering works; only the parallel measurement is broken.

Fix

Isolate each query in its own project dir so a claude -p only ever sees its own command file:

python
import tempfile, shutil
iso_root = Path(tempfile.mkdtemp(prefix="trigeval-"))
project_commands_dir = iso_root / ".claude" / "commands"
command_file = project_commands_dir / f"{clean_name}.md"
try:
    project_commands_dir.mkdir(parents=True, exist_ok=True)
    command_file.write_text(command_content)
    # ... Popen(..., cwd=str(iso_root), env=env)
finally:
    shutil.rmtree(iso_root, ignore_errors=True)

This removes the cross-matching entirely (each worker's clean_name is the only skill in its tree) and lets parallel runs stay fast and correct.

Workaround until then

Pass --num-workers 1 to run_eval.py / run_loop.py. Note run_loop.py forwards --num-workers to run_eval, so the same bug silently corrupts the optimization signal at the default of 10 — every iteration optimizes against ~0 trigger rates.

Found while triggering-testing a new skill; happy to send a PR with the isolation fix if useful.