[BUG] Conditional (paths:) skills can never activate in auto mode — the trigger is wired only to Read/Edit/Write, which bashFirst tells the model not to use
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Conditional skills (those declaring paths: in SKILL.md frontmatter) are activated only by the Read, Edit and Write tools. Auto mode's bashFirst system prompt (#88041) instructs the model to do all file I/O through Bash instead of those three tools. The two features are individually reasonable and mutually exclusive in practice: in an auto-mode session, a paths:-scoped skill can never activate, no matter how many matching files the model reads and writes.
This is a root cause for the "trigger never fires" symptom of #62049 (closed as stale, never diagnosed).
I hit this on a Ruby TUI project whose CLAUDE.md says, unconditionally, "read the tuile skill before designing components". The skill declares paths: ["**/*.rb"]. I spent a whole session editing .rb files — the skill never appeared, and Skill(tuile) returned Unknown skill: tuile. Every file access had gone through cat / sed / heredocs, as auto mode directs.
What Should Happen?
Either:
- Bash file access should also feed the conditional-skill matcher. Paths are already extracted from Bash commands elsewhere in the product (permission checks, file-write detection), so the signal exists.
- Or
bashFirstshould not be injected when the session has conditional skills loaded — the cheap fix, at the cost of leaving conditional skills dependent on tool choice.
Option 1 is the real fix: as long as activation is tied to three specific tool names, any future prompt or model that prefers a different path to the filesystem will silently switch these skills off again.
Steps to Reproduce
- Create
~/.claude/skills/zebra/SKILL.md:
```yaml
---
name: zebra
description: Test skill. Says ZEBRA.
paths:
- "**/*.rb"
---
Say ZEBRA.
```
- Start a session in a repo containing
.rbfiles, with auto mode enabled. - Ask for a change to a
.rbfile. Auto mode steers the edit through Bash (sed, heredoc,python3). - The skill never appears in the available-skills listing;
/zebraandSkill(zebra)returnUnknown skill: zebra. - Now do one
Readof the same.rbfile. The skill activates and appears in the listing.
Step 5 is the control: the mechanism works, it is just unreachable through the tool auto mode tells the model to use.
Evidence
From the 2.1.247 native binary.
The loader parses paths: into a gitignore-style matcher, keeps such skills in a conditionalSkills map (not in the listing), and promotes them to dynamicSkills on a path match:
for (let [r, o] of Pv().conditionalSkills) {
if (o.type !== "prompt" || !o.paths || o.paths.length === 0) continue;
let s = Rjr.default().add(D9(o.paths, "skill_paths")); // the `ignore` package
for (let i of e) {
let a = o1t(i) ? Ajr(t, i) : i; // absolute -> relative to cwd
if (!a || a.startsWith("..") || o1t(a)) continue;
if (s.ignores(a)) {
Pv().dynamicSkills.set(m9e(o), o),
Pv().conditionalSkills.delete(r),
Pv().activatedConditionalSkillNames.add(r),
n.push(r),
_(`[skills] Activated conditional skill '${r}' (matched path: ${a})`);
break
}
}
}
Reached through one entry point:
async function bJ(e, t, n, r) {
let o = ze(), s = await gLs(e, [t], o, r);
if (s.length > 0) { ...; w9e(s).then(() => { Sjr([t], o) }).catch(() => {}) }
Sjr([t], o)
}
bJ has exactly three call sites, all guarded by !CLAUDE_CODE_SIMPLE && remoteCall === undefined:
- the Edit handler —
await bJ(t.session.host, g, c, t.storageV5) - the Write handler —
await bJ(n.session.host, d, u, n.storageV5) - the Read handler —
await bJ(o.session.host, p, o.dynamicSkillDirTriggers, o.storageV5)
No Bash path reaches it. Relevant telemetry: tengu_dynamic_skills_changed with source: "conditional_paths".
Meanwhile the bashFirst auto-mode injection (quoted in full in #88041) says:
Do your work through the Bash tool wherever it can accomplish the job: read files with cat, head, or sed -n, search with grep and find, and make file changes with sed, heredocs, or short scripts, rather than using the dedicated Read, Edit, or Write tools.
Those are precisely the three tools activation is hooked to.
Correlation check on this install: of 10 personal skills, the 5 declaring paths: were all absent from the session's available-skills listing; the 5 without it were all present.
Notes
- Because an unactivated conditional skill is also unreachable by name (#49835), a project
CLAUDE.mdthat instructs "read skill X" cannot be obeyed — the model getsUnknown skill, with nothing to indicate the skill exists but is dormant. A dormant conditional skill arguably deserves a distinct error, or a listing entry marked as such. - Suggested labels:
area:skills,area:tools,area:core.
Is this a regression?
I don't know — bashFirst is the newer half of the pair.
Claude Code Version
2.1.247 (Claude Code), native build
Platform
Anthropic API
Operating System
Linux (7.0.0-29-generic)
Terminal/Shell
fish
Additional Information
Related:
- #88041 — the
bashFirstauto-mode instruction itself (this is a further consequence of it) - #62049 — "
paths:never triggers skill auto-loading", closed as stale; this is a root cause for it - #49835 —
paths:makes a skill undiscoverable and unreachable by name
3 Comments
Same root cause, but the blast radius is wider than
paths:skills. Three additions from a Windows repro on 2.1.267.1. The same Read/Edit/Write-only trigger also gates nested-directory skill discovery and subdirectory
CLAUDE.md.Skills in
<subdir>/.claude/skills/(the monorepo layout from the docs, nopaths:frontmatter) and the<subdir>/CLAUDE.md/AGENTS.mdchain stay unloaded after any number of Bash reads of files under<subdir>. One Read tool call on any of those files loads the skill and the wholeCLAUDE.mdchain at once. That variant is a different consumer of the trigger than theconditionalSkillsmatcher quoted above, and may be a different code path, so I filed it separately with a minimal repro as #93403. If the fix here covers it too, feel free to close that one as a duplicate.2. The dedicated Grep tool does not trigger it either.
Grep gets the path as a structured argument, so this is not only a shell-parsing problem. Checked on a second, unrelated subtree with its own nested skill: Grep with an explicit path under it loaded nothing, Read on the same file loaded the skill immediately. Whatever feeds the matcher should include Grep and Glob targets as well as Bash paths.
3. Reproduces on Windows, so
platform:linuxis too narrow.Windows 11 Pro 10.0.26200, Claude Code 2.1.267, Windows Terminal with a cmd.exe tab, Bash tool backed by Git Bash, auto mode, model
claude-fable-5-1.This has being on since 2.1.231 and is much worse than just not following rules with paths. See https://github.com/anthropics/claude-code/issues/88405#issuecomment-5483255046
Confirming on macOS (Darwin 25.6) with Claude Code 2.1.281, the latest release, so it still reproduces on current versions. Adding a platform data point, since the report and comments so far cover Linux and Windows.
Minimal A/B repro that isolates
pathsas the only variable~/.claude/skills/test-with-paths/SKILL.mddeclaringpaths: "**/zz-trigger.md"~/.claude/skills/test-without-paths/SKILL.mdwith nopathstest-without-paths.Skill(test-without-paths)runs.Skill(test-with-paths)fails withUnknown skill: test-with-paths. Did you mean test-without-paths?Readof a file matching the pattern: the listing immediately announcestest-with-paths, and invoking it now works.Real-world impact
Three stack-specific skills (legacy React microfrontends, Vite Module Federation, React Native) used
pathsto stay out of unrelated repos. In auto mode they were never registered, so custom slash commands that invoke them by name at the start of a code review failed every time, even inside the matching repos. The only signal wasUnknown skill; nothing in the UI suggests the skill exists on disk but is dormant, which sent us looking for naming or frontmatter errors first.Workaround we settled on
Removing
pathsfrom our skills and rules, and accepting the cost of listing them in every session (about 700 tokens for the three descriptions). Doing aReadof a matching file before invoking also works, but it depends on the model choosingRead, which is exactly what auto mode discourages.Two small asks while the root fix lands
Skill(name)targets a skill that exists on disk but is gated bypaths, say that in the error instead ofUnknown skill./skillswith a state like "waiting for a matching file", so users can tell them apart from missing ones.