Unlike the other v1 categories, a settings-fragment file isn’t copied to a fixed location in ~/.claude/. Its JSON content is deep-merged into your existing ~/.claude/settings.json, with care taken to preserve every key the user already added by hand.
The fragment shape
A settings-fragment body must contain exactly one JSON code block under a ## fragment heading:
## fragment
```json
{
"permissions": {
"allow": [
"Bash(bundle exec:*)",
"Bash(rake:*)"
]
}
}
```
The validator at _meta/validate.mjs parses the fragment and rejects entries that touch keys outside permissions.{allow, deny, ask} and env. This is a v1 safety constraint — broader settings keys (e.g. hooks, model selection) need separate review.
Install: deep-merge with backup
When you install a settings-fragment, tools/install.sh does this:
-
Backs up the existing
~/.claude/settings.jsontosettings.json.bak.<timestamp>. -
Reads the existing settings and the new fragment.
-
Deep-merges with these specific behaviours:
-
permissions.allow[],permissions.deny[],permissions.ask[]— concatenated and de-duplicated. Order is preserved byjq’s `unique(which sorts). -
Top-level non-array keys (e.g.
env) — shallow-merged; the fragment wins on conflicts.
-
-
Writes the merged result back to
settings.json.
The merged-fragment JSON is recorded in ~/.claude/.memory-files-manifest.json under the entry’s merged-fragment field. This is the key to clean uninstall.
Uninstall: subtract the recorded fragment
tools/uninstall.sh does the inverse: reads the manifest’s merged-fragment, subtracts each entry (allow[], deny[], ask[]) from the current settings.json, and writes the result. Hand-curated entries the user added separately survive — only the manifest-recorded entries are removed.
|
This is the property the smoke test exercises:
The smoke test runs this in CI on every PR (see |
Update: re-merge with subtraction first
When upstream changes a fragment (e.g. adds a new permission, removes a stale one), tools/install.sh --update <slug> does:
-
Subtracts the old manifest-recorded fragment first — so removed entries actually disappear, not linger.
-
Merges the new upstream fragment.
-
Records the new fragment in the manifest for the next round.
Net effect: the update is transactional. Hand-curated entries still survive; upstream removals propagate cleanly.
Why these five categories merge instead of copy
The deep-merge model exists specifically because settings.json is shared state — multiple seeds may legitimately add entries to the same keys. A naive "copy" model would either clobber existing settings (bad) or force users to manually splice multiple fragments together (worse). Deep-merge with manifest-recorded subtraction is the only model that handles the multi-fragment, multi-uninstall, multi-update case cleanly.
Worked v1 seeds
The four v1 settings-fragments together cover the most common Ribose-style allowlists:
-
ruby-bundler-allowlist—bundle exec:*,rake:*,rubocop:*,rspec:*,ruby -e:*. -
github-cli-reads-allowlist—gh issue,gh pr view:,gh pr list:*,gh api:*,gh repo view:*(read-only by design). -
common-unix-reads-allowlist—grep:*,find:*,ls:*,cat:*,head:*,tail:*,wc:*. -
web-research-allowlist—WebSearch, plusWebFetch(domain:…)fordocs.anthropic.com,code.claude.com,github.com.
See also
-
SCHEMA.md— settings-fragment type rules. -
tools/install.shandtools/uninstall.shin the repo — the actual merge / subtract implementations. -
tools/test-install.sh— the smoke test that exercises the hand-curated-entry preservation case.