[Bug]: Every user-scope Claude skill is labelled "Project Skill" — packaged backendCwd is $HOME, so <cwd>/.claude/skills collides with the user config root
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Searched: #6449, #7871, #3040, #5487, #4544. Those cover which skills are discovered. This is about the scope they are discovered with, which is wrong for every packaged desktop install.
Area
apps/server
Summary
In a packaged desktop build, every user-scope Claude skill is reported as scope: "project" and rendered in the $ picker with a "Project Skill" badge. The badge is the only scope signal in the picker, and it is wrong for 100% of entries on a machine with no repo-local skills.
Steps to reproduce
- Have Claude skills in
~/.claude/skills/and no.claude/skillsor.agents/skillsin the open repository. - Open T3 Code desktop (packaged), Claude provider.
- Type
$in the composer. Every row is badged Project Skill. - Confirm in the provider cache:
python3 -c "
import json,os,collections
d=json.load(open(os.path.expanduser('~/.t3/caches/claudeAgent.json')))
f=[]
def w(o):
if isinstance(o,dict):
for k,v in o.items():
if k=='skills' and isinstance(v,list): f.append(v)
else: w(v)
elif isinstance(o,list): [w(x) for x in o]
w(d)
print(len(f[0]), collections.Counter(s.get('scope') for s in f[0]))
print(f[0][0])
"
Output on both of my machines: every entry scope='project', every path under ~/.claude/skills/.
Cause
apps/desktop/dist-electron/main.cjs starts the backend in the user's home directory whenever the app is packaged:
backendCwd: input.isPackaged ? homeDirectory : appRoot,
That value becomes process.cwd() for the server, which becomes ServerConfig.cwd, which ClaudeDriver reads and passes straight to discovery:
const { cwd } = yield* ServerConfig;
...
const skills = yield* discoverClaudeSkills(claudeSettings, cwd, resolvedEnvironment);
discoverClaudeSkills then builds its roots from that same cwd:
const roots = [
{ directory: path.join(configDirPath, "skills"), scope: "user" },
...cwd ? [{ directory: path.join(cwd, ".agents", "skills"), scope: "project" },
{ directory: path.join(cwd, ".claude", "skills"), scope: "project" }] : []
];
With cwd === $HOME, root 3 resolves to $HOME/.claude/skills — byte-for-byte the same directory as root 1. The loop writes into a Map keyed by skill name and lets later roots win, so every user skill is re-written with scope: "project". Root 2 does the same to ~/.agents/skills, the Agent Skills standard user directory.
Verified against the shipped app.asar of 0.0.36, which is identical to main here.
Verified on two machines
Both after a clean quit and relaunch, so the cache is freshly built:
| machine | backend process cwd | skills cached | scope tally |
|---|---|---|---|
| MacBook | /Users/<user> |
44 | {'project': 44} |
| Mac mini | /Users/<user> |
40 | {'project': 40} |
Neither repository contains .claude/skills or .agents/skills. Every one of those 84 labels is wrong.
Expected behavior
Skills found in the user config directory report scope: "user" and render without the "Project Skill" badge.
Actual behavior
They report scope: "project" and render as "Project Skill", so the picker cannot distinguish a genuine repo-local skill from a global one — the exact distinction the badge exists to make.
Proposed guard
Independent of the larger question in #6449 of which cwd the probe should use, this specific collision can be closed in discoverClaudeSkills by skipping a project root that resolves to the user root:
const userRoot = path.resolve(path.join(configDirPath, "skills"));
const roots = [
{ directory: path.join(configDirPath, "skills"), scope: "user" },
...(cwd ? [
{ directory: path.join(cwd, ".agents", "skills"), scope: "project" },
{ directory: path.join(cwd, ".claude", "skills"), scope: "project" },
].filter((root) => path.resolve(root.directory) !== userRoot) : []),
];
That fixes every packaged install without changing which skills are found.
Impact
Major degradation or frequent failure
Version or commit
T3 Code (Alpha) 0.0.36, packaged desktop. Cause confirmed present in current main.
Environment
macOS 26.5.2 (Apple Silicon), two machines, Claude provider, claude CLI 2.1.251 and 2.1.250.
Workaround
None. The badge cannot be corrected from settings.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apps/server by locating discoverClaudeSkills and the roots built from ServerConfig.cwd; compare the user skills root with the project roots when cwd is the home directory. Check the packaged entry point in apps/desktop/dist-electron/main.cjs and reproduce the cache behavior described in the issue. Done means user-directory skills retain scope: "user" while genuine repository skills remain project-scoped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend, desktop
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100