[BUG] One account-wide Skills library is applied to three environments with different capabilities — chat-authored rules leak into Claude Code (web) and override native tooling, producing incorrect results
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?
One account-level Skills library is applied to three environments that do not have the same capabilities: claude.ai Chat, Cowork, and Claude Code on the web. Skills authored under one environment's constraints are injected into the others and acted upon, even when the premise they state about the environment is false there.
The environments are not interchangeable:
| | claude.ai Chat | Cowork | Claude Code (web) |
|---|---|---|---|
| Runtime | container sandbox | container sandbox | managed VM on a repo |
| Language toolchains | preinstalled only | preinstalled only | full (cargo, lockfile, registry cache) |
| Version control | none | none | git |
| Deleting a file | unrecoverable | unrecoverable | recoverable via git |
| Unit of delivery | chat artifact | document / file output | commit, branch, PR |
Concrete failure. I maintain a skill that forbids guessing third-party API signatures from model memory and requires source-level verification first. It was written in Chat, where there is no Rust toolchain, so it hard-codes the only path available there: fetch crate source over HTTP (index.crates.io → resolve version → static.crates.io → download → untar → read).
In a Claude Code web session on a Rust repo, that skill still fires. The agent takes the HTTP path and verifies against the newest version published on crates.io instead of the version Cargo.lock actually resolves to — in an environment where cargo tree / cargo add / the local registry cache were available the whole time.
Why this is a correctness bug rather than an inefficiency:
- It verifies the wrong artifact. Published-latest ≠ resolved version. The lockfile, feature unification, and transitive constraints are invisible over HTTP. What was verified is not what compiles.
- It inverts the skill's own purpose. The rule exists to prevent memory-based API guessing; the leaked variant can confirm a signature that does not exist in the resolved version, then report success. False confidence is worse than no verification, because downstream code is written on top of it.
- It is silent. No warning reaches me, and the agent's own view is that it complied. The only way I found it was noticing the agent take an absurd route in an environment with a one-command answer.
- It overrides a native capability with an inferior one.
The underlying defect is in scoping, not in my skill. Skills are scoped by where they are stored (the account) rather than by where their preconditions hold (the execution environment):
- The
SKILL.mdschema has no environment or capability field. A user who knows a skill is environment-specific has no way to declare it. The only channel is prose indescription, which is advisory text, not a loader-enforced condition. - There is no per-environment on/off control in the Skills UI, so a skill enabled for Chat is silently enabled everywhere the library reaches.
- No documented precedence states that a runtime-detected capability supersedes a static assumption asserted in injected text — so the injected text wins by default.
- There is no way inside a Code web session to list what was injected and from where, so the boundary is invisible.
Same defect, other manifestations: chat-scoped rules that ban rm outright (proportionate in a sandbox with no VCS, obstructive in a git repo where rm -rf target/ and git clean are routine); and chat/Cowork delivery conventions (version stamps, output-directory delivery, encoding handling) that are meaningless in a repo and leak into diffs.
General invariant being violated: rules derived from an environment's limitations are guaranteed to be wrong in an environment that lacks those limitations. Every such rule becomes a defect the moment the library crosses environments.
What Should Happen?
For the specific repro: the Claude Code web session should either
- (a) not load a skill scoped to a different execution environment; or
- (b) load it, evaluate its stated premise ("this environment has no Rust toolchain") against the actual runtime, find it false, and skip the rule.
In both cases the agent verifies against the source of the version Cargo.lock actually resolves to, using cargo tree / cargo add / the local registry cache / cargo doc.
Systemically, skill visibility should be assignable per environment. Minimum bar for a fix:
- Per-skill, per-environment assignment in the claude.ai Skills UI — a checkbox matrix, one row per skill:
| Skill | Chat | Cowork | Claude Code (web) |
|---|---|---|---|
| dep-source-verify | ☑ | ☐ | ☐ |
| file-ops-guard | ☑ | ☑ | ☐ |
with columns extended as further environments come to share the same library.
- Default-deny cross-environment propagation. A skill is enabled on the environment it was added on; every other column is opt-in. Widening the blast radius should be a deliberate act.
- A global escape hatch, shippable ahead of the full matrix: do not apply claude.ai account Skills and preferences to Claude Code sessions.
- Declarative scoping in
SKILL.mdfrontmatter, so scope travels with the skill and survives export/import and version control:
surfaces: [chat, cowork]
requires-capabilities: [rust-toolchain]
conflicts-with-capabilities: [rust-toolchain] # env has a better way; skip
Capability predicates are the durable form. My skill does not actually care whether it is "chat" — it cares whether a toolchain exists. Environment names will drift; capability predicates will not.
- Documented precedence: a runtime-detected capability supersedes a static assumption in injected configuration.
- The same scoping for account-level user preferences, which are currently one global block applied everywhere.
- An inspection command in Code sessions: loaded skills → source (account library / repo
.claude/skills/ plugin) → scope.
Acceptance criteria
- [ ] Each skill can be independently enabled or disabled for Chat, Cowork, and Claude Code (web).
- [ ] A skill disabled for Claude Code (web) is verifiably not loaded there, confirmable via a listing command rather than by inference.
- [ ] Default behavior never widens a skill's reach beyond the environment it was added on.
- [ ] Account-level user preferences honor the same scoping, or offer a Code-session opt-out.
- [ ] Scope is expressible in
SKILL.mdfrontmatter. - [ ] Precedence of environment capability over injected static assumptions is documented.
Error Messages/Logs
No error is emitted. That is part of the defect: the failure is a silently wrong method choice, not a crash, so nothing is logged and nothing surfaces to the user.
Behavioral trace (summarized, not a verbatim transcript):
Observed in Claude Code (web), Rust repo with Cargo.toml + Cargo.lock:
1. agent announces source-level verification of dependency <crate>
2. agent fetches https://index.crates.io/... to resolve "latest version"
3. agent downloads https://static.crates.io/.../<crate>-<latest>.crate
4. agent untars, greps the source, reports the signature as verified
5. agent writes code against that signature
-> verified version = latest published, NOT the version Cargo.lock resolves to
-> no warning, no diagnostic, exit status success
Expected in the same session:
1. cargo tree / cargo metadata -> resolved version for this workspace
2. read source of that exact version from the local registry cache
3. write code against the signature that will actually compile
If a session-side record of injected account-level Skills exists, the loaded set for that session is what should be inspected; there is currently no user-facing command to dump it.
Steps to Reproduce
- In claude.ai Chat, add a personal skill whose instructions encode a premise that is true only in the chat sandbox. Minimal example —
dep-source-verify/SKILL.md:
---
name: dep-source-verify
description: >
Before writing code against a third-party crate, never guess the API from memory.
This environment has no Rust toolchain, so fetch the crate source over HTTP:
index.crates.io -> resolve version -> static.crates.io -> download -> untar -> read.
Report the verified signature before writing any code.
---
# Dependency source verification
1. Resolve the crate version from index.crates.io.
2. Download the .crate tarball from static.crates.io.
3. Untar it and read the real source for the API signature.
4. Only then write code that calls the API.
Confirm it is enabled in the account Skills settings.
- Prepare a small Rust repository containing a dependency whose API changed between versions, and pin an older version so the resolved version differs from the newest published one:
# Cargo.toml
[package]
name = "repro"
version = "0.1.0"
edition = "2021"
[dependencies]
<crate> = "=<older-version>"
Commit Cargo.lock so the resolved version is unambiguous.
- Open a Claude Code on the web session on that repository.
- Prompt: "Add a call to
<crate>::<api>. Verify the signature against the real source before writing the code." - Observe the method the agent chooses.
Actual: the chat-authored skill fires; the agent fetches from index.crates.io / static.crates.io and verifies against the newest published version, not the pinned resolved one. Where the API differs between those versions, the verification result is wrong while being reported as successful.
Expected: either the skill is not loaded in this environment, or its false premise is detected and the rule skipped; the agent resolves the version via cargo and reads the source for that version.
Notes on reproduction:
- The failure is deterministic in shape but the exact trigger depends on the skill description matching the prompt, as with any skill.
- The same class of failure reproduces with any skill whose instructions are written around a missing capability: e.g. a chat-scoped rule banning
rmwill also block routinerm -rf target/orgit cleanin a repo session.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
all env
Platform
Other
Operating System
Other
Terminal/Shell
Terminal.app (macOS)
Additional Information
_No response_