Project-scoped Claude Code plugin skills load globally, leaking into unrelated sessions (plugin counterpart of #457)
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 72/100
Research direction
Start in crates/jcode-base/src/skill.rs, reading installed_plugin_paths and the load_global/load_project_overlay paths, then compare the session-scoped overlay introduced for #457. Verify the three described cases: the owning workspace and nested directories, an unrelated workspace, and the global pass. Done means local plugins load only for their project tree, user-scoped and legacy unscoped installs remain global, and symlinked workspaces compare correctly.
Written by the indexing model from the issue text.
Description
Summary
Claude Code lets you install a plugin either for the user (global) or for a single project ("local" scope). jcode loads both kinds into the shared global skill registry, so a plugin installed for one repo contributes its skills to every session's prompt.
Concretely: on my machine figma and notion are installed for a single work repo, and their ~10 skill descriptions show up in the prompt of every session in every other project, where they can never be relevant.
This is the plugin-install counterpart of #457, which fixed the same leak for ./.agents/skills project-local directories. That fix introduced the session-scoped overlay; plugin installs never got routed through it.
Environment
- macOS 26.4.1, jcode master (v0.81.4)
- Claude Code plugin manifest at
~/.claude/plugins/installed_plugins.json
Repro
-
In Claude Code, install a plugin with local (project) scope in repo A — e.g.
figma. -
Confirm
~/.claude/plugins/installed_plugins.jsonrecords it with"scope": "local"and aprojectPathpointing at repo A:"figma@claude-plugins-official": [ { "scope": "local", "projectPath": "/Users/me/work/repo-a", "installPath": "/Users/me/.claude/plugins/cache/claude-plugins-official/figma/2.2.68" } ] -
Open a jcode session in an unrelated repo B.
-
The
figma-*skills are listed as available.
Expected: they appear only in sessions whose workspace is repo A (or a directory nested inside it).
Cause
crates/jcode-base/src/skill.rs, installed_plugin_paths reads only installPath and discards the rest of the install record:
for install in installs {
if let Some(path) = install.get("installPath").and_then(|p| p.as_str()) {
let path = PathBuf::from(path);
if path.is_dir() {
paths.push(path);
}
}
}
Every returned path is then scanned during load_global, so scope and projectPath never influence which registry a plugin's skills land in.
Impact
Mostly prompt pollution, and it scales with how many project-scoped plugins the user has installed across all their repos: every one of them is described in every session, in every project. It also blurs the guarantee #457 established, that project-local skills belong to one workspace.
Suggested fix
Read scope and projectPath alongside installPath and split the load into two passes:
- global pass (
load_global) takes only unscoped/user-scoped installs; - workspace pass (
load_project_overlay) takes only installs whoseprojectPathis the session's workspace root or a parent of it, so nested directories inside the project still match.
Two details worth calling out:
- An install can be
"scope": "user"and still record theprojectPathit happened to be installed from. Scope should win, otherwise a user-scoped plugin would silently become project-only. - The legacy unscoped layouts (
repos/, and the barecache/fallback used when the manifest is missing) carry no scope information, so they belong to the global pass only. Letting the overlay re-scan them would undo the isolation and make every overlay load do a full plugin tree walk.
Symlinked workspaces need canonical-path comparison for the containment check.
I have a patch with tests for the three cases (owning workspace incl. nested dirs, unrelated workspace, global pass) and can open a PR.
Investigated and written with agent assistance.
Agent: jcode v0.64.160-dev (claude-opus-5)
- Dominant language
- Rust
- Stars
- 19.9k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 30
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.
More from 1jehuang/jcode
-
OrcaRouter is in the login catalog but has no ProviderChoice, so it can't be selected explicitly Open
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
-
Session::save() skips untouched sessions, so is_debug and canary never persist (4 failing tests) Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
area: providers autonomous: clear bug triage: reproducible
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
area: providers autonomous: no bug triage: needs-decision
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100