[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

Status Open
Reported on v2.1.247
Maintainer reply None cached
Activity 3 comments · opened Aug 27, 2026

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:

  1. 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.
  2. Or bashFirst should 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

  1. Create ~/.claude/skills/zebra/SKILL.md:

```yaml
---
name: zebra
description: Test skill. Says ZEBRA.
paths:

  • "**/*.rb"

---
Say ZEBRA.
```

  1. Start a session in a repo containing .rb files, with auto mode enabled.
  2. Ask for a change to a .rb file. Auto mode steers the edit through Bash (sed, heredoc, python3).
  3. The skill never appears in the available-skills listing; /zebra and Skill(zebra) return Unknown skill: zebra.
  4. Now do one Read of the same .rb file. 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.md that instructs "read skill X" cannot be obeyed — the model gets Unknown 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 bashFirst auto-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

View original on GitHub ↗

3 Comments

alexandret-unity · 1 month ago

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, no paths: frontmatter) and the <subdir>/CLAUDE.md / AGENTS.md chain 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 whole CLAUDE.md chain at once. That variant is a different consumer of the trigger than the conditionalSkills matcher 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:linux is 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.

ARivottiC · 1 month ago

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

davidsh7 · 20 days ago

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 paths as the only variable

  1. Two personal skills, identical except for one frontmatter line:
  • ~/.claude/skills/test-with-paths/SKILL.md declaring paths: "**/zz-trigger.md"
  • ~/.claude/skills/test-without-paths/SKILL.md with no paths
  1. New session in auto mode. The available-skills listing shows only test-without-paths.
  2. Skill(test-without-paths) runs. Skill(test-with-paths) fails with Unknown skill: test-with-paths. Did you mean test-without-paths?
  3. A single Read of a file matching the pattern: the listing immediately announces test-with-paths, and invoking it now works.

Real-world impact

Three stack-specific skills (legacy React microfrontends, Vite Module Federation, React Native) used paths to 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 was Unknown 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 paths from our skills and rules, and accepting the cost of listing them in every session (about 700 tokens for the three descriptions). Doing a Read of a matching file before invoking also works, but it depends on the model choosing Read, which is exactly what auto mode discourages.

Two small asks while the root fix lands

  • When Skill(name) targets a skill that exists on disk but is gated by paths, say that in the error instead of Unknown skill.
  • Show dormant conditional skills in /skills with a state like "waiting for a matching file", so users can tell them apart from missing ones.