Built-in `fewer-permission-prompts` skill auto-fires on Agent tool dispatches, hijacking sub-agent tasks (real billable cost)

Resolved 💬 3 comments Opened May 4, 2026 by Studnicky Closed May 8, 2026

Summary

The built-in fewer-permission-prompts skill (registered inside the Claude Code binary, userInvocable: true) is matching against user-issued Agent tool dispatches and silently replacing the dispatch prompt — the user's actual instructions are demoted to an ## Additional instructions from the user footnote that the sub-agent treats as advisory and largely ignores.

The result: sub-agents faithfully execute the canned skill task (scan ~/.claude/projects/*.jsonl, build a Bash/MCP allowlist, write .claude/settings.json under permissions.allow) instead of the work they were dispatched to do.

Impact

This is real money out of consumers' pockets. In one session it cost me:

  • 5 sub-agent dispatches hijacked across haiku and sonnet tiers
  • ~225K tokens per Sonnet sub-agent burned executing the wrong task (consistent across two independent dispatches: 228,954 and 225,649 total_tokens reported in the agent transcripts)
  • 8.4M ms wall time (~140 minutes) of paid model time across the two Sonnet hijacks alone — plus equivalent waste across three Haiku attempts that also drifted
  • All work product discarded — the sub-agents wrote files, modified state, generated reports, all on the wrong task. Hours of cleanup to revert
  • Cascading retries: as I tightened my prompt to be more explicit ("DO NOT modify settings, DO NOT add permissions, DO NOT touch .claude/"), the auto-skill-matcher kept firing anyway because the keywords I was forbidding were the same keywords driving the match
  • Investigation cost: more model time spent reverse-engineering whose fault this was. The agents' own reports were hallucinated — they confidently claimed to have done their assigned task in summary form, while their actual edits were the skill's task

The user has no signal this is happening. The dispatch returns "completed", the agent's final message looks like a normal report, the cost meter just goes up.

Reproduction

The skill is registered in 2.1.128 (and likely earlier) at:

T$({
  name: \"fewer-permission-prompts\",
  description: \"Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.\",
  userInvocable: true,
  async getPromptForCommand(userInput) {
    let prompt = Hb3;  // the ~4KB canned skill prompt
    if (userInput) prompt += \`\\n## Additional instructions from the user\\n\${userInput}\`;
    return [{ type: \"text\", text: prompt }];
  }
})

When the canned prompt is prepended to the dispatch, the sub-agent reads the canned task as the primary directive and the user's actual dispatch as a footnote. Trigger condition appears to be keyword overlap with the skill description — any dispatch prompt mentioning permissions, allowlist, settings.json, MCP, or Bash in plausible-enough context can match.

Root issue

Auto-matching userInvocable skills to explicit Agent tool dispatches is wrong. When a parent agent constructs a precise dispatch prompt and calls Agent({ prompt: \"...\" }), that prompt is the contract. The harness should pass it through unmodified. Skill auto-matching makes sense for the user's own typed prompts at the top level — not for programmatic sub-agent invocation where the calling agent has already done its own intent matching.

This is silent prompt rewriting on a paid API call, with no surfaced indicator to the user, and no opt-out I could find.

Asks

  1. Don't run skill auto-matching on Agent/Task tool dispatches. Pass the parent's prompt through verbatim. Skills are for end-user-typed prompts.
  2. If the matcher must run on dispatches, either log a clear notice to the parent context (\"dispatched prompt was matched to skill X and prepended\") or flip userInvocable: true skills to opt-in via explicit /skill invocation, so they don't fire on programmatic dispatches.
  3. Surface a per-turn cost breakdown at the parent level when a sub-agent burns thousands of dollars worth of tokens on a hijacked dispatch. Right now the only signal is the bill.

Environment

  • Claude Code 2.1.128 (binary install at ~/.local/share/claude/versions/)
  • macOS arm64
  • Reproduced on both Haiku 4.5 and Sonnet 4.6 sub-agent dispatches

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗