Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/claude/global-claude-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ The phrase must be in the last message the human typed — plain text, text sent
- Reads the local branch version from `$REPO_DIR/global-settings/VERSION` (if a local repo is configured).
- Compares all versions using semver and prints a colored status panel to stderr (visible in the terminal/CLI).
- **Derives the managed file list** (since v2.7.0) from the hook scripts the canonical `settings.json` registers, fetched from the same source as `VERSION`; the update notice emits one command block per registered hook, plus `settings.json` first and `VERSION` last. Hook names are validated before they are placed in a command. If the canonical `settings.json` cannot be fetched, the list falls back to the installed one and a warning says so.
- **Verifies the installed files** (since v2.7.0) when the versions match: every registered hook against its canonical copy (`sha256`, trailing newlines ignored) and `settings.json` as parsed JSON without `model`. A missing or changed file produces the "files out of sync" notice with repair blocks for those files only; a canonical copy that could not be fetched is reported as not verified. The panel gains a `Files` line (`7/7 verified ✓`, or the missing/changed counts).
- **Verifies the installed files** (since v2.7.0) when the versions match: every registered hook against its canonical copy (`sha256`, trailing newlines ignored) and `settings.json` as parsed JSON without the keys Claude Code writes when you pick a model or an effort level (`model`, `modelSettings`, `effortLevel`, `fastMode`, `advisorModel`, `switchModelsOnFlag`; since v2.7.1, before that only `model`). A missing or changed file produces the "files out of sync" notice with repair blocks for those files only; a canonical copy that could not be fetched is reported as not verified. The panel gains a `Files` line (`7/7 verified ✓`, or the missing/changed counts).
- Always injects a session-start message into Claude's context via stdout — "up to date", "update required", "files out of sync", or "configuration error" — which Claude relays at the top of its first response.
- Never silently skips: configuration issues (missing config files, unreachable remote, `curl` not installed) are shown in the panel and forwarded to Claude.

Expand Down
13 changes: 12 additions & 1 deletion global-settings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,18 @@ The file blocks in the notice are not a hand-maintained list. `check-settings-ve
When the installed version already matches the online one, the hook also compares every managed file with its canonical copy:

- hook scripts by `sha256`, ignoring trailing newlines (the `printf '%s\n'` install writes exactly one, the `git show` install copies the bytes verbatim; neither is drift);
- `settings.json` as parsed JSON, without the `model` key. The VSCode model picker writes `{"model": …}` into it under relock option (B), and a model choice is a preference, not policy. Every other difference in that file counts, and the notice names the top-level keys that differ.
- `settings.json` as parsed JSON, without the top-level keys Claude Code itself writes there when you pick a model or an effort level (v2.7.1; v2.7.0 skipped only `model`). Under relock option (B) these writes land in the file, and a model choice is a preference, not policy:

| Key | Written by |
| --- | --- |
| `model` | the VSCode model picker and `/model` |
| `modelSettings` | `/effort` and the effort picker, saved per model as `modelSettings.<model>.effortLevel` |
| `effortLevel` | the same choice when it cannot be keyed by model |
| `fastMode` | `/fast` |
| `advisorModel` | `/advisor` |
| `switchModelsOnFlag` | the VSCode toggle that switches model when a safeguard flags a message |

Every other difference in that file counts, and the notice names the top-level keys that differ.

A missing or changed file turns the session-start message into **FILES OUT OF SYNC**. It names the files, the phrase for step 3 is **"repair my global settings"**, and the unlock, contract and relock steps are the same as for an update. Only the affected files are rewritten; `VERSION` is left alone. On the GitHub path the check costs two extra fetches per session (`settings.json`, then every registered hook in one `curl` call); on the git-fetch path it costs none. A canonical file that could not be fetched is reported as not verified, never as fine.

Expand Down
2 changes: 1 addition & 1 deletion global-settings/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.7.0
2.7.1
25 changes: 19 additions & 6 deletions global-settings/check-settings-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,29 @@ norm_sha() {
fi
}

# Top-level keys Claude Code itself writes into ~/.claude/settings.json when the
# user picks a model or an effort level, which is exactly what relock option (B)
# allows. They are preferences, not policy (README → Updating, step 4):
# model the model picker and /model
# modelSettings /effort and the effort picker, saved per model as
# modelSettings.<model>.effortLevel
# effortLevel the same choice when it cannot be keyed by model
# fastMode /fast
# advisorModel /advisor
# switchModelsOnFlag the VSCode toggle that switches model when a safeguard
# flags a message
# Everything else in that file is policy.
CLIENT_MODEL_KEYS='["model","modelSettings","effortLevel","fastMode","advisorModel","switchModelsOnFlag"]'

# settings_diff <installed> <canonical> — empty when both settings.json files are
# the same document, otherwise the top-level keys that differ. Compared as parsed
# JSON (formatting is not drift) and without `model`: the VSCode extension
# writes {"model": …} into ~/.claude/settings.json on every model switch, which
# is exactly what relock option (B) allows, and a model choice is a preference,
# not policy (README → Updating, step 4). Everything else in that file is.
# JSON (formatting is not drift) and without CLIENT_MODEL_KEYS.
settings_diff() {
local a b keys
a=$(jq -S 'del(.model)' "$1" 2>/dev/null) || { echo "not valid JSON"; return; }
b=$(jq -S 'del(.model)' "$2" 2>/dev/null) || { echo "canonical copy not valid JSON"; return; }
a=$(jq -S --argjson k "$CLIENT_MODEL_KEYS" 'delpaths($k | map([.]))' "$1" 2>/dev/null) \
|| { echo "not valid JSON"; return; }
b=$(jq -S --argjson k "$CLIENT_MODEL_KEYS" 'delpaths($k | map([.]))' "$2" 2>/dev/null) \
|| { echo "canonical copy not valid JSON"; return; }
[ "$a" = "$b" ] && return
keys=$(jq -n -r --argjson a "$a" --argjson b "$b" \
'[($a | keys[]), ($b | keys[])] | unique | map(select($a[.] != $b[.])) | join(", ")' 2>/dev/null)
Expand Down
24 changes: 22 additions & 2 deletions global-settings/tests/test-check-settings-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
# - a matching install is reported up to date, with every file verified
# - a missing or changed hook, or a changed settings.json, is reported
# FILES OUT OF SYNC with repair blocks for those files only
# - a settings.json that only gained `model` (the VSCode picker's write under
# relock option B) and a trailing-newline difference are not drift
# - a settings.json that only gained `model`, `modelSettings` or another
# model-choice key (the picker's and /effort's writes under relock
# option B) and a trailing-newline difference are not drift
#
# Usage:
# ./tests/test-check-settings-version.sh # run all
Expand Down Expand Up @@ -210,6 +211,25 @@ run_hook
lacks 'OUT OF SYNC'; check "model: settings.json with only model added is not drift" $?
has 'Settings are up to date'; check "model: reported up to date" $?

# ── 7b. the effort and other model-choice keys are not drift either ─────────
install_all
jq '.model = "claude-opus-5-5[1m]"
| .modelSettings = {"claude-opus-5-5": {"effortLevel": "low"}}
| .effortLevel = "high" | .fastMode = true
| .advisorModel = "opus" | .switchModelsOnFlag = false' \
"$CANON_GS/settings.json" > "$CLAUDE_DIR/settings.json"
run_hook
lacks 'OUT OF SYNC'; check "effort: model-choice keys are not drift" $?
has 'Settings are up to date'; check "effort: reported up to date" $?

# ── 7c. a model-choice key next to a policy change still reports the policy ─
install_all
jq '.modelSettings = {"claude-opus-5-5": {"effortLevel": "low"}}
| .permissions.allow += ["Bash(rm -rf /)"]' \
"$CANON_GS/settings.json" > "$CLAUDE_DIR/settings.json"
run_hook
has 'changed : settings.json (top-level keys: permissions)'; check "effort: only the policy key is named" $?

# ── 8. settings.json with a permission change is drift, and names the key ────
install_all
jq '.permissions.allow += ["Bash(rm -rf /)"]' "$CANON_GS/settings.json" > "$CLAUDE_DIR/settings.json"
Expand Down
Loading