Skip to content

fix(schema-hygiene): retire //-stub pattern + drop unvalidatable skillOverrides - #60

Merged
tigers1997 merged 1 commit into
mainfrom
fix/schema-hygiene-strip-doc-stubs
May 24, 2026
Merged

fix(schema-hygiene): retire //-stub pattern + drop unvalidatable skillOverrides#60
tigers1997 merged 1 commit into
mainfrom
fix/schema-hygiene-strip-doc-stubs

Conversation

@tigers1997

Copy link
Copy Markdown
Owner

Summary

Dogfood from upgrading an adjacent project surfaced two real Claude Code settings-validator complaints. Both are configurator bugs introduced by PRs #17, #18, #56, and #57.

Bug 1 — skillOverrides: "name-only" (string form, PR #57). The current Claude Code schema requires the per-skill object map ({"skill-name": "name-only"}); the string form was based on an incorrect assumption documented in the tier-pro patch's // comment. Verified against code.claude.com/docs/en/settings: only object form is documented, and the setting explicitly does not apply to plugin skills, further narrowing its usefulness as a tier-wide default.

Bug 2 — // foo "commented opt-in stub" pattern (PRs #17, #18, #56). These literal top-level keys propagate from source patches into the user's .claude/settings.json, where Claude Code's editor schema rejects them as unknown properties. PR #56's _is_doc_label filter intentionally kept // foo keys while stripping bare // and //<digits>; the dogfood proves that intent was wrong.

Third bug found during verification: compute_merged_settings applies the doc-label filter to file-based patches (lines 473, 488) but bypasses inline extraSettings (line 477-478). The ui module's bare "//" keys were also leaking to user settings.json — not just the // foo stubs.

What changes

File Change
configure.py _is_doc_label strips ALL //-prefixed keys; new _strip_doc_labels() recursive helper applied at end of compute_merged_settings; extraSettings merge filter added; new check_settings_validates() runtime preflight; new static --check step asserts skillOverrides shape
templates/token-efficiency/settings-patch.tier-pro.json Deleted skillOverrides: "name-only"; replaced docstring with corrected rationale
templates/safety/settings-patch.json, templates/multi-agent/settings-patch.json, templates/git-workflow/settings-patch.json Deleted all // foo opt-in stubs; updated //N docstrings to point at the new opt-in home
config_schema.py (ui module) Deleted inline // hideVimModeIndicator + // subagentStatusLine from extraSettings
templates/core/dot-claude/settings.local.json.example New home for user-discoverable opt-in stubs (the .example suffix means CC doesn't parse this file directly, so // foo stubs are safe). Covers: sandbox, worktree, prUrlTemplate, subagentStatusLine + sibling notes on hideVimModeIndicator, skillOverrides
test/schema-hygiene/ 4 new tests covering: no-stubs-rendered (all 5 personas), skillOverrides shape, preflight detection (4 violation classes), recursive strip invariant
CHANGELOG.md Entry under ## Unreleased

Verification

The new static --check step fires on the current bug when run for the first time (before the fix is applied). After the fix:

✓ all checks passed
  modules: 12
  scanned: 81 files under templates/
  Claude Code compat: 2.1.116–2.1.150

Rendered settings.json under solo-experienced now has top-level keys: ['$schema', 'autoMode', 'env', 'hooks', 'model', 'permissions', 'statusLine'] — zero //-prefixed keys at any depth, no skillOverrides.

All 5 persona snapshot fixtures still match (file paths unchanged; we changed content only).

Test plan

  • All 64 tests pass locally (60 existing + 4 new)
  • python3 configure.py --check passes (would have failed before the tier-pro fix — verified via the new skillOverrides-shape static check fired on the bug)
  • End-to-end scaffold for all 5 personas produces validator-clean .claude/settings.json
  • Persona snapshot fixtures still match (content change only, no file path drift)
  • Pre-commit code review (5-angle) ran via the Explore agent; two real findings addressed (broken // statusLineVim stub — uncommenting produced wrong top-level key; broken // envCap stub — synthetic key name); third finding (placeholder keys in // skillOverrides) addressed by removing the placeholders
  • CI green (will verify after PR opens)

Compat for downstream users

Users on cc-configure 2.6.0 with a populated .claude/settings.json get the cleanup on next cc-configure --retrofit. The deep-merge preserves their customizations; the new strip removes the stub-leaks. Users who manually uncommented and activated an opt-in keep the activated value (it's a real key like sandbox, not a //-prefixed one).

Users who want one of the formerly-shipped opt-ins (sandbox, worktree, prUrlTemplate, subagentStatusLine, skillOverrides) can copy the stub from settings.local.json.example into their personal settings.local.json and uncomment.

🤖 Generated with Claude Code

…lOverrides

Dogfood from upgrading an adjacent project to the latest configurator
version surfaced two real Claude Code settings-validator complaints:

1. `templates/token-efficiency/settings-patch.tier-pro.json` shipped
   `"skillOverrides": "name-only"` (string form), but the current Claude
   Code schema requires the per-skill object map. The string form was
   based on a comment in the patch file that claimed a global "apply to
   all skills" form existed; verification against the official doc
   (code.claude.com/docs/en/settings) shows it never did. Also: the
   setting explicitly doesn't apply to plugin skills, further narrowing
   its usefulness as a tier-wide default.

2. The `// foo` "commented opt-in stub" pattern propagated literal
   top-level keys into the user's `.claude/settings.json`. PR #56's
   `_is_doc_label` filter intentionally kept `// foo` stubs (stripping
   only bare `//` and `//<digits>`) so users could uncomment them.
   Empirically, those propagated keys trigger schema-validator complaints
   in the user's editor — the intent was wrong.

A third bug was found during verification: `compute_merged_settings`
applies the doc-label filter to file-based patches but bypasses inline
`extraSettings` (ui module). So the ui module's bare `"//"` doc-labels
were also leaking to user settings.json.

Fixes:
  - `_is_doc_label` now strips ALL `//`-prefixed keys.
  - New `_strip_doc_labels()` recursively scrubs merged settings —
    catches nested cases (e.g., `statusLine.// hideVimModeIndicator`)
    the shallow per-merge filters miss.
  - `extraSettings` merge path now applies the filter.
  - `skillOverrides: "name-only"` deleted from tier-pro (replaced with
    a docstring explaining why no default is shipped).
  - All `// foo` stubs deleted from 4 source patch files + the ui
    module's inline `extraSettings`.
  - Opt-in discovery moved to
    `templates/core/dot-claude/settings.local.json.example` — the
    `.example` suffix means Claude Code doesn't parse it directly, so
    `// foo` stubs there are safe.
  - New `check_settings_validates()` runtime preflight emits a
    `[ SETTINGS WARNINGS ]` block when violations are detected.
  - New static `--check` step asserts no patch file ships
    `skillOverrides` as a non-object (regression guard — fires on the
    current bug when first run).

New tests under `test/schema-hygiene/`:
  - All 5 personas render settings.json with zero `//`-prefixed keys
  - No settings-patch ships `skillOverrides` as a non-object
  - `check_settings_validates` catches 4 violation classes + clean case
  - `_strip_doc_labels` recursively strips at all depths

Compat: existing users get the cleanup on next `cc-configure --retrofit`.
Deep-merge preserves their customizations; the new strip removes leaks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@tigers1997
tigers1997 merged commit c265c4d into main May 24, 2026
1 check passed
@tigers1997
tigers1997 deleted the fix/schema-hygiene-strip-doc-stubs branch May 24, 2026 14:49
tigers1997 added a commit that referenced this pull request May 30, 2026
… F1+F3) (#73)

deep_merge_settings deduped hook groups by whole-dict ==, so when the
configurator moves a hook from a standalone matcher group to a bundled one
(post-2.6.0 safety: lone block-dangerous-bash -> [block-dangerous-bash,
check-package-availability]), both groups survived a retrofit and the shared
command fired twice on every matching call. PR #61 had scoped this out as
"rare"; the dogfood shows it's the dominant upgrade for safety-module users.

- New _merge_hook_groups: key by matcher, union inner hooks[] by command;
  append a command only when absent from every same-matcher group. Preserves
  user customizations (PR #61 test still passes) + old N+1 self-heal.
- F3: check_settings_validates gains _find_duplicate_hook_commands, flagging a
  command wired >1x under the same (event, matcher); cross-matcher allowed.
- [ MERGED ] summary gains an "N hook command(s)" counter.
- CI: wire test/retrofit-hooks/ and test/schema-hygiene/ into check.yml —
  created by PRs #60/#61 but never run in CI until now.
- New TDD fixtures: test-standalone-bundled-merge.sh (fails on old code),
  test-duplicate-hook-commands.sh. Adversarially reviewed; non-list hooks[]
  append path hardened.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant