Skip to content

Skill picker lists the server's startup-directory skills, not the active project's #6449

Description

@Brechard

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

discoverClaudeSkills scans <cwd>/.claude/skills for project-scope skills, and that cwd traces back to ServerConfig.cwd, which is resolved once at server startup from the cwd CLI argument or process.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 requests skills/list with cwds: [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

  1. Have a repo with skills in .claude/skills/<name>/SKILL.md.
  2. Start the server from a different directory than that repo (the normal case for the desktop app).
  3. Open that repo as a project, open a thread, type $ in the composer.

Expected: the project's skills appear alongside the user's global ~/.claude/skills, with the user copy winning a name collision.

Correction (2026-08-28): this line originally said project scope should win a collision, "matching Claude Code's most-specific-wins resolution". That was wrong. Measured against claude 2.1.250, the user copy wins, with a project-only control run proving the project copy loads on its own. Evidence is in this comment. Correcting it in place rather than only in a comment because an acceptance case is being assembled from this issue, and the original wording would reinstate the inverted precedence.

Actual: only the global user skills are listed. A project skill sharing a name with a user skill does not override it.

Note

ProviderRegistry depends only on ServerConfig and ProviderInstanceRegistry, 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.

Activity

balazsegyed commented on Aug 23, 2026

@balazsegyed

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.

janb-sc commented on Aug 24, 2026

@janb-sc

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 == home

With 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:

  1. the open project's repo-local skills are missing from $, and
  2. the user's own global skills are mislabeled project scope.

/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.

archiekd commented on Aug 24, 2026

@archiekd

@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.

javierscode commented on Aug 26, 2026

@javierscode

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:

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 tagged Project Skill

Related


Filed via t3 triage — Claude Opus 5 (claude-opus-5) in Claude Code.

t3dotgg commented on Aug 27, 2026

@t3dotgg
Member

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.

BretJohnson commented on Aug 28, 2026

@BretJohnson

Still repros for me, version 0.0.35, on Windows WSL, using both Claude and Codex.

Here's additional information reported by ChatGPT doing troubleshooting, if it's useful:

Image

t3dotgg commented on Aug 28, 2026

@t3dotgg
Member

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.

Brechard commented on Aug 28, 2026

@Brechard
ContributorAuthor

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.)

skovtunenko commented on Aug 29, 2026

@skovtunenko

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.

mrsOwlex commented on Aug 31, 2026

@mrsOwlex

I tried addressing this issue in #8756
Really would appreciate feedback

andybergon commented on Sep 1, 2026

@andybergon

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.

Mnigos commented on Sep 1, 2026

@Mnigos
Contributor

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.

gui-complypro commented on Sep 6, 2026

@gui-complypro

@maria-rcks closed this but it's still an issue - at least with codex.

Mnigos commented on Sep 6, 2026

@Mnigos
Contributor

@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.

skovtunenko commented on Sep 7, 2026

@skovtunenko

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.

altaywtf commented on Sep 7, 2026

@altaywtf

fixed and working great thanks for the amazing work folks 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions