Repository navigation
Skill picker lists the server's startup-directory skills, not the active project's #6449
Description
Activity
$ picker in packaged desktop builds #4544 as a duplicate of this issue Still reproduces on today's nightly, 0.0.34-nightly.20260823.1164 on macOS.
Fresh provider cache (~/.t3/caches/claudeAgent.json) after the new build's probe: 23 skills, every one scope: "project", every path under ~/.claude/skills/. My actual project skill (<repo>/.claude/skills/deploy) is absent from the $ picker and from the / command list (103 commands, user scope only). So both symptoms folded in here from #4544 and #4658 are still present.
The impact is wider than the picker itself. Project skills are invisible in every packaged desktop install, and every user skill carries a wrong "Project" badge, so the scope labels can't be trusted at all.
It also looks like a small fix at this point: discoverClaudeSkills already takes a cwd parameter, and the clients already compute the active project root. Three PRs with the same basic approach of resolving against the workspace root are sitting unreviewed (#6450, #7882, #7909). I'm on the nightly channel and happy to verify whichever one you want to land.
Confirming this on the Claude provider (macOS), with a cache snapshot that also explains the "user skills show up as project skills" symptom some people are hitting.
Environment: T3 Code (Nightly), macOS (Darwin 25.6.0), claude-agent provider 2.1.241, Node 26.7.0.
Open project: /Users/jan/web — repo skills at /Users/jan/web/.claude/skills/: seo-publish, gsc-audit, verify.
User skills: ~/.claude/skills/: grill, unslop, writing-for-agents.
The $ picker shows only the three user skills and lists none of the three repo skills. ~/.t3/caches/claudeAgent.json skills array:
[
{"name":"grill","scope":"project","path":"/Users/jan/.claude/skills/grill/SKILL.md"},
{"name":"unslop","scope":"project","path":"/Users/jan/.claude/skills/unslop/SKILL.md"},
{"name":"writing-for-agents","scope":"project","path":"/Users/jan/.claude/skills/writing-for-agents/SKILL.md"}
]Two things stand out: every path is under ~/.claude/skills, and every entry is tagged scope: "project" (not user). No /Users/jan/web/.claude/skills/* path appears at all.
This is exactly what the two-root scan (discoverClaudeSkills, per #5622) produces when ServerConfig.cwd resolves to the home directory /Users/jan:
{ directory: path.join(configDirPath, "skills"), scope: "user" }, // /Users/jan/.claude/skills
{ directory: path.join(cwd, ".claude", "skills"), scope: "project" }, // /Users/jan/.claude/skills <-- same dir when cwd == homeWith cwd == /Users/jan, the "project" root collapses onto the "user" root. The project scan just re-reads the user skills and stamps them scope: "project", while the actually-open project at /Users/jan/web is never visited. So the same misresolved cwd causes both visible symptoms at once:
- the open project's repo-local skills are missing from
$, and - the user's own global skills are mislabeled
projectscope.
/reload-skills returns 29 skills available (no changes) and does not rescan the open project; fully restarting the app and deleting ~/.t3/caches/claudeAgent.json do not help either — consistent with the wrong cwd being re-resolved the same way each time. The skills stay invocable by name (the SDK session has them loaded), so this is discovery/labeling only, matching your writeup.
@t3dotgg @juliusmarminge It would be great to get this sorted. I have multiple projects with different sets of skills so my skills aren't all top user level. At the moment I have having to type them fully out manually and it is easy to make a mistake when remembering the skill name.
Still reproducing on the latest nightly (0.0.35-nightly.20260826.1195, macOS arm64 (Darwin 25.5.0), desktop app against local folders). Adding a fresh data point that I think is worth folding into this issue, because it gives the bug a visible tell in the UI that makes it diagnosable without reading any logs.
The tell: user-scope skills are labelled Project Skill
In my install the $ picker lists only my home-directory skills — and every one of them is tagged Project Skill. Not a single skill from the project the thread is open in appears at all.
That mislabelling is a direct consequence of the root cause described above. My desktop-app server has its cwd at my home directory, so the two project-scope roots collapse onto the user-scope one:
Root scanned (ClaudeSkills.ts:104-110) |
Resolves to, with cwd = $HOME |
Scope assigned |
|---|---|---|
<configDir>/skills |
~/.claude/skills |
user |
<cwd>/.agents/skills |
~/.agents/skills |
project |
<cwd>/.claude/skills |
~/.claude/skills |
project |
Because later roots win on name collision, every user skill is discovered a second time under project scope and the project label is the one that survives into the snapshot. So the symptom is not just "project skills are missing" — it's also "user skills are actively misreported as project skills", which is what made this look like a scoping bug rather than a missing directory on my end.
This is a useful signal for triage: if a user reports that all their skills are tagged Project Skill and none of their actual project skills show up, the server cwd is their home directory.
Verification on my machine
Server cwd confirmed to be the home directory, not the open project:
$ lsof -a -p <server-pid> -d cwd
COMMAND PID USER FD TYPE NAME
T3 Code ... cwd DIR <home directory> # <- home, not the open project
Every entry in the picker traces back to either ~/.claude/skills or ~/.agents/skills. The open project has a double-digit number of skills under .claude/skills/<name>/SKILL.md, none of which appear. I ruled out the obvious local causes:
- All of them have a
SKILL.mdwith valid YAML frontmatter and aname:field (so this is not [Bug]: Claude skill discovery drops skills with frontmatter Claude Code itself accepts (strict YAML vs CLI leniency), silently #7757). - My project reaches its skills through a symlinked
.claudedirectory, and there is a symlinked entry inside.claude/skills/too — but neither is the cause: discovery never reaches that path at all, since<cwd>is the home directory. - One skill name exists at both user and project scope. Per the merge rules the project copy should win; instead the user-scope copy is what gets served, mislabelled as
project.
Confirmed still unfixed on main
I checked main today: ClaudeDriver.ts:126 still reads const { cwd } = yield* ServerConfig, and ClaudeSkills.ts:104-110 still builds both project roots from that value. The full chain is unchanged:
cli/config.ts:282 (process.cwd(), resolved once at bootstrap) → ServerConfig.cwd → ClaudeDriver.ts:126 → ClaudeProvider.ts:923 (discoverClaudeSkills(claudeSettings, cwd, …)) → ClaudeSkills.ts:104-110.
Worth noting the contrast that makes the fix direction concrete: turns already resolve the right directory via resolveThreadWorkspaceCwd (checkpointing/Utils.ts:12, used from ProviderCommandReactor.ts:651/962/1141). So the correct path is already computed server-side for the execution path — it just never reaches skill discovery.
(Line references taken from the v0.0.34 tag and re-checked against main; identical in both.)
On the silent-failure note
Confirming the point about discovery being silent. My server.trace.ndjson has repeated discoverClaudeSkills spans, all {"exit":{"_tag":"Success"}}, with "attributes":{} — neither the cwd used nor the number of skills found is recorded:
{"type":"effect-span","name":"discoverClaudeSkills","durationMs":4.71,"attributes":{},"exit":{"_tag":"Success"}}Recording the resolved cwd and the per-root skill count as span attributes would have made this self-diagnosing.
Practical impact
Because the Claude CLI itself runs in the project directory, project skills are still reachable by asking for them in prose ("use the <name> skill") — they just cannot be invoked through $. So the blast radius is the picker and $// invocation, not skill execution. That is a usable workaround, but it means the discoverability of every project-scoped skill is effectively zero for desktop-app users.
Environment
- T3 Code desktop app (Nightly):
0.0.35-nightly.20260826.1195 - macOS arm64 (Darwin 25.5.0), Node v22.17.0
- Surface: desktop app against local folders
- Provider: Claude, CLI
2.1.246 - Server: launched by the desktop app,
cwd= home directory - Skills on disk: a handful at user scope (
~/.claude/skills,~/.agents/skills), a double-digit number at project scope - Skills listed in
$picker: only the user-scope ones, all taggedProject Skill
Related
- [Bug] Project-scope skill discovery uses stale global cwd, never the active worktree — .claude/skills/<name> never surfaces in the composer picker #7871 — closed as a duplicate of this issue.
Filed via t3 triage — Claude Opus 5 (claude-opus-5) in Claude Code.
Related report #3040 covers the same problem and adds useful evidence.
Issue 6449 explicitly names the Codex skills/list cwd problem as well as Claude discovery. Issue 3040 supplies an A/B probe where changing only cwds finds or loses the same repository skill, and later comments reproduce the same startup-directory leakage across Claude projects. Current main still sends process.cwd to Codex discovery and ServerConfig.cwd to Claude discovery. This is one missing active-project scope, not two provider-specific missing-directory formats.
I'm linking the report here before I close it as a duplicate during an automated pass. The source report and its attachments will remain available at #3040.
Note
🤖 GPT-5.6 Sol responding on behalf of Theo
Related report #8449 covers the same startup-directory skill discovery bug and adds a useful acceptance case.
Its project stores craft-eager-loading and merge-to-stage under .agents/skills, with .claude/skills symlinked to that directory. The Claude CLI finds both after /reload-skills, but T3's Claude provider cache does not. The cache mtime is newer than the skill files, so this is not a stale-cache timing case.
Please keep that symlink layout in the acceptance test for the active-project directory fix. Both skill names must appear in T3 after refresh. This also checks that a directory fix does not hide a separate symlink traversal bug. The underlying discovery bug remains open here.
Note
🤖 Claude Opus 5 writing on behalf of Rodrigo
Two corrections to this issue from me, both measured against the CLI while working on #7673. One of them matters for the acceptance case @t3dotgg is assembling.
1. The "Expected" line above is wrong, and it is mine. I wrote:
the project's skills appear alongside the user's global
~/.claude/skills, with project scope winning a name collision (matching Claude Code's most-specific-wins resolution)
Claude Code resolves the opposite way. With the same skill directory name in each scope and a different one-word body in each, claude -p (2.1.250) returns the user copy every time:
| scopes present | resolved |
|---|---|
user + .claude + .agents |
USERSCOPE |
user + .claude |
USERSCOPE |
user + .agents |
USERSCOPE |
.claude only (control) |
PROJECTSCOPE |
The control is the part that makes it conclusive: the project skill loads fine on its own, so the first three rows are the user copy genuinely winning a collision rather than the project root being ignored. #7673 flips discoverClaudeSkills to match. Flagging it here because whoever fixes the active-project scope will be working in the same function, and implementing this issue as written would put the inverted precedence straight back.
2. .agents/skills is not a Claude Code skill root, so the #8449 symlink case is worth reading carefully. A skill that exists only under <workspace>/.agents/skills gets Unknown command from claude 2.1.250. In #8449 the CLI finds craft-eager-loading and merge-to-stage because that project has .claude/skills symlinked to .agents/skills — the CLI is reading them through .claude/skills, not through native .agents support.
That is good news for the acceptance test, since discoverClaudeSkills already scans <cwd>/.claude/skills and readDirectory follows a symlinked directory. It just means the test is exercising the symlink, not .agents discovery, and should not be read as evidence that we need the latter. Separately, we currently scan .agents/skills directly and report what we find there — skills the CLI cannot actually run. That is its own small bug and I have not touched it in #7673.
On the overlap with #7671, since a dedup pass is in progress: they are adjacent but not the same defect. This issue is about which directory gets scanned — get it wrong and the project's skills are missing entirely and user skills wear a "Project" badge. #7671 is about which of the entries found are offered and what syntax picking one inserts — get it wrong and the picker lists skills that cannot run and quietly inserts the form that fails. Fixing either one leaves the other fully intact, which is why #7673 does nothing for the reports in this thread. (My earlier cross-reference on #8295 pointed at #7671 for the wrong-directory problem; it should have pointed here.)
Note
🤖 Claude Opus 5 writing on behalf of the reporter
Reproduces on T3 Code (Alpha) 0.0.36, macOS 26.6.2 arm64 (Darwin 25.6.0), Node v24.20.0, claude-agent provider 2.1.251.
Paths below are redacted: $HOME is my home directory, <project> is the open project root (a directory outside $HOME's top level, unrelated to it).
Adding two data points I didn't see in the thread yet.
1. Both cwds are observable side by side in one process tree.
The server resolves its cwd to $HOME, and that same server spawns the turn with the correct project root — so the divergence the writeup describes ("used when running turns but never for skill discovery") is directly measurable, not inferred:
$ lsof -a -p 20503 -d cwd -Fn # server: app.asar/apps/server/dist/bin.mjs
n$HOME
$ lsof -a -p 86956 -d cwd -Fn # the `claude` CLI it spawned for the turn
n<project>
And the CLI is launched with the project root passed explicitly:
claude --output-format stream-json ... --add-dir <project>
So the right path is already in the server's hands at spawn time. Discovery just doesn't use it.
2. skills[] and slashCommands[] in the same cache file disagree about scope.
~/.t3/caches/claudeAgent.json:
count: 4
scopes: { "project": 4 }
paths:
$HOME/.claude/skills/doc-coauthoring/SKILL.md
$HOME/.claude/skills/pdf/SKILL.md
$HOME/.claude/skills/pptx/SKILL.md
$HOME/.claude/skills/skill-creator/SKILL.md
Standard signature: 4 skills, all from $HOME/.claude/skills, all stamped scope: "project". But the derived slash commands describe those same four entries as (user):
{ "name": "pdf", "description": "Use this skill whenever ... use this skill. (user)" }One cache file, two contradictory scope labels for the same four skills. Whichever path builds slashCommands[] descriptions has the scope right, so it may be a usable reference for the fix.
Scale of what's missing: the open project has 53 SKILL.md under .claude/skills/ and 51 under .agents/skills/. None are discovered — the picker lists 4.
Symlinks ruled out. Unlike @javierscode's setup, neither .claude, .agents, nor any entry inside .claude/skills/ is a symlink here (fd -HI -t l -d 1 . .claude/skills → 0). Plain directories, same result.
Confirming discovery/labeling-only: every one of the 53 project skills is loaded in the SDK session and invocable by name — only the $// listing is wrong. That makes the current workaround "type the name blind", which is exactly what @archiekd describes above.
I tried addressing this issue in #8756
Really would appreciate feedback
Still reproduces in T3 Code (Alpha) 0.0.37 on native Windows with Codex.
The active worktree contains a valid project skill at .agents/skills/<skill>/SKILL.md. In the same thread:
- Codex's session skill catalog includes the project skill, and the model can use it when named in prose.
- Typing
$<prefix>in T3's composer lists Personal and App skills but omits that Project skill.
That narrows the failure to T3's composer/provider inventory, not malformed skill frontmatter or Codex failing to discover the active worktree. The 0.0.37 source still probes Codex skills/list from process.cwd().
Workaround: mention the full skill name in prose.
Took a stab at this in #9090. The server now resolves the cwd from the thread's worktree (or the project root) and asks Codex/Claude for that workspace's skills, so the picker stops listing whatever repo the server happened to start in. Sent $skill chips survive too, since the timeline reads the same catalog. Bot reviews are green, screenshot's in the PR.
@maria-rcks closed this but it's still an issue - at least with codex.
@gui-complypro Which build are you on? Both halves of this only exist in nightly so far: the Codex/OpenCode side landed in #8778 and the Claude side in #9210, both on September 2, first shipped in 0.0.39-nightly.20260902.1256. Stable 0.0.38 was cut the day before and still lists the server's startup directory. If you already see this on a current nightly, the project path and whether the thread runs in a worktree would help narrow it down, since the picker falls back to the machine-wide list whenever the per-workspace snapshot for that cwd is missing.
In the latest stable 0.0.39 (6abdf37) version, I can see that my local (project) skills are being picked by the harness -- previously it was a bug, now it is fixed.
fixed and working great thanks for the amazing work folks 🙏

Note
🤖 Claude Opus 5 writing on behalf of Rodrigo
Summary
The composer's
$skill picker discovers skills from the server process's own startup directory rather than from the project the user has open. With more than one project configured, the picker shows another project's skills — or none at all.Cause
discoverClaudeSkillsscans<cwd>/.claude/skillsfor project-scope skills, and thatcwdtraces back toServerConfig.cwd, which is resolved once at server startup from thecwdCLI argument orprocess.cwd()(apps/server/src/cli/config.ts). It is a single process-wide value, not per-project.The path that actually matters — the thread's worktree, else the project's
workspaceRoot— is used when running turns (resolveThreadWorkspaceCwd) but never for skill discovery. The Codex provider has the same shape: it requestsskills/listwithcwds: [process.cwd()].Discovery is also silent about failures: an unreadable skills directory, an unreadable
SKILL.md, and malformed YAML frontmatter are each swallowed with no log, so a misconfigured or wrongly-scoped path is indistinguishable from "this project has no skills".Reproduction
.claude/skills/<name>/SKILL.md.$in the composer.Expected: the project's skills appear alongside the user's global
~/.claude/skills, with the user copy winning a name collision.Actual: only the global user skills are listed. A project skill sharing a name with a user skill does not override it.
Note
ProviderRegistrydepends only onServerConfigandProviderInstanceRegistry, so it cannot resolve which project a client is viewing. The clients already compute that path, which suggests the workspace should travel in the request rather than being inferred server-side.