[Bug] skillOverrides in user/project settings has no effect — g7H() is a stub returning "on"

Resolved 💬 2 comments Opened Apr 19, 2026 by 3351163616 Closed May 4, 2026

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 reads
policySettings and flagSettings — never userSettings or
projectSettings. 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

  1. Add the following to ~/.claude/settings.json:

``json
{
"skillOverrides": {
"claude-api": "off",
"review": "user-invocable-only",
"simplify": "name-only"
}
}
``

  1. Start a new Claude Code session in any directory.
  1. Ask the model: What skills do you have?

Expected

  • claude-api and review should not appear in the skill listing.
  • simplify should 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 /skills in 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 via bv1
  • flagSettings.skillOverrides (CLI flag) → works via bv1
  • H.disableModelInvocation on the skill itself → works via bv1
  • checkPermissions @ 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 skillOverrides in ~/.claude/settings.json or

.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 reusing
Cv1'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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗