[Bug] skillOverrides in user/project settings has no effect — g7H() is a stub returning "on"
TL;DR
skillOverrides in user- and project-level settings.json does not hide
skills from the model's system-prompt skill listing. Reverse-engineering
the bundled CLI shows the gate function g7H() is a stub returning"on" unconditionally, and the override resolver bv1() only readspolicySettings and flagSettings — never userSettings orprojectSettings. As a result, setting a skill to "off","user-invocable-only", or "name-only" in user settings still leaks
the full skill description into every system prompt.
Environment
- Claude Code:
2.1.114 - OS: Windows 11 Pro (also likely affects macOS/Linux — same bundle)
- Install location:
claude.exe(SEA-packed Node binary)
Reproduction
- Add the following to
~/.claude/settings.json:
``json``
{
"skillOverrides": {
"claude-api": "off",
"review": "user-invocable-only",
"simplify": "name-only"
}
}
- Start a new Claude Code session in any directory.
- Ask the model:
What skills do you have?
Expected
claude-apiandreviewshould not appear in the skill listing.simplifyshould appear as name only, without its description.- Total system-prompt token cost should drop accordingly.
Actual
- All 9 bundled skills are listed with full descriptions, as if
skillOverrides were absent.
- Asking
/skillsin the UI does reflect the overrides correctly
(icons/colors) — so user settings are being read, just not used by
the prompt-construction path.
Root cause (from reverse-engineering the bundle)
Two problems, both in the same area of the bundle:
1. g7H() is a stub
function g7H(H){return "on"}
function MR1(H){ // "hidden from model?"
let $ = g7H(H); // always "on"
return $ === "user-invocable-only" || $ === "off"; // always false
}
function YR1(H){ // "include in listing?"
return H.type === "prompt"
&& !H.disableModelInvocation
&& !MR1(H) // !false — always passes
&& (H.source === "builtin" || ...);
}
function kl1(H){ // "name-only?"
return g7H(H) === "name-only"; // always false
}
All three gates that decide whether a skill enters the prompt listing
(or is rendered name-only) delegate to g7H, which has been replaced
by a stub. This looks like a placeholder that was never wired up to
the real resolver.
2. bv1() skips user/project settings
The other resolver in the same module does read settings, but only
the admin-side ones:
function bv1(H, $) {
let q = N6("policySettings")?.skillOverrides?.[$];
if (q) return { value: q, source: "policy" };
let K = N6("flagSettings")?.skillOverrides?.[$];
if (K) return { value: K, source: "flag" };
if (H.disableModelInvocation) return { value: "user-invocable-only", source: "author" };
if (H.source === "plugin") return { value: "on", source: "plugin" };
return; // never checks userSettings / projectSettings
}
// userSettings/projectSettings are only read by Cv1(), which is
// called from the /skills React component (UI display), not from
// prompt construction.
function Cv1(H){
return N6("projectSettings")?.skillOverrides?.[H]
?? N6("userSettings")?.skillOverrides?.[H];
}
So even if g7H were fixed to delegate to a resolver, bv1 would
still need a userSettings/projectSettings branch.
What does work today
policySettings.skillOverrides(managed settings) → works viabv1flagSettings.skillOverrides(CLI flag) → works viabv1H.disableModelInvocationon the skill itself → works viabv1checkPermissions@ the Skill tool call site does read the
effective override and refuses off/user-invocable-only model
invocations — but by that point the description has already been
sent to the model.
Impact
- Users who configure
skillOverridesin~/.claude/settings.jsonor
.claude/settings.json see no change in system-prompt size.
- The documented
"off"/"user-invocable-only"/"name-only"
behavior ("hides it from the model") is not delivered.
- Per-turn token cost for the skill listing is fixed at full size
(roughly 5–10k tokens for the 9 bundled skills).
Suggested fix
Wire g7H (and by extension MR1/kl1/YR1) to a resolver that
also consults userSettings and projectSettings, e.g. by reusingCv1's lookup. A minimal sketch:
function g7H(H){
return bv1(H, H.name)?.value
?? Cv1(H.name) // user/project override
?? "on";
}
This would bring runtime behavior in line with the JSON-schema
description:
skillOverrides: Per-skill listing overrides keyed by skill name."name-only"lists the skill without its description;"user-invocable-only"hides it from the model but keeps/name;"off"hides it from both. Absent = on.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗