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
3 changes: 3 additions & 0 deletions .github/workflows/hook-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ jobs:

- name: Run no-polling hook tests
run: bash global-settings/tests/test-block-polling.sh

- name: Run version-check hook tests
run: bash global-settings/tests/test-check-settings-version.sh
13 changes: 12 additions & 1 deletion docs/claude/global-claude-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ The source name is included so a stale mirror is visible at a glance: `GitHub` f

The relock step always offers both options, each with its own command, and says why (B) is there. It is the same choice described under [Troubleshooting → keep `settings.json` unlocked](#model-or-the-model-picker-fails-with-eperm-operation-not-permitted-open-claudesettingsjson). Since v2.5.2 it is part of the update message itself, not a note in the docs.

**Files out of sync** (prominently displayed, since v2.7.0):

> NEW SESSION — GLOBAL CLAUDE SETTINGS: FILES OUT OF SYNC
> Installed: v2.7.0 (matches origin/main), but 1 of 7 installed files does not match the canonical copies
> missing: block-polling.sh
> Say "repair my global settings" to reinstall the listed files.

The version matched, but a file the canonical `settings.json` registers is missing or differs from its canonical copy. The notice carries the same unlock, contract and relock steps as an update and a command block for each affected file only. See [Which files the notice installs, and what "up to date" means](../../global-settings/README.md#which-files-the-notice-installs-and-what-up-to-date-means-v270) for what is compared and what is deliberately not counted as drift.

**Configuration error** (prominently displayed):

> NEW SESSION — GLOBAL CLAUDE SETTINGS: CONFIGURATION ERROR
Expand Down Expand Up @@ -353,7 +362,9 @@ The phrase must be in the last message the human typed — plain text, text sent
- **Git fetch (fallback):** If the GitHub method is not configured or fails, and `~/.claude/settings-repo-path` points to a valid local clone, fetches via `git fetch origin <ref> --depth=1` and reads the `VERSION` file from that ref.
- 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).
- Always injects a session-start message into Claude's context via stdout — "up to date", "update required", or "configuration error" — which Claude relays at the top of its first response.
- **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).
- 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.

## Optional: notification sounds (opt-in)
Expand Down
11 changes: 11 additions & 0 deletions global-settings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ When you see a version warning at session start:

> ⚠️ Don't skip step 4. Without it, the kernel-level protection stays off until the next time you run `sudo chattr +i`. The hooks still defend in depth, but the strongest layer is unarmed. (B) unlocks one file on purpose. Leaving `hooks/*.sh` unlocked as well disarms the guards themselves.

### Which files the notice installs, and what "up to date" means (v2.7.0)

The file blocks in the notice are not a hand-maintained list. `check-settings-version.sh` reads the canonical `settings.json` from the same source it took `VERSION` from and emits one block per hook script it registers, plus `settings.json` itself first and `VERSION` last. A hook that is wired is therefore a hook that gets installed. Before v2.7.0 the list lived in the script and fell behind `settings.json` twice: v2.4.1 added two hooks it had missed, and `block-polling.sh`, registered since v2.5.0, was never on it, so following the notice left that guard uninstalled while the version read as current.

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.

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.

### The update contract

Claude reinstalls each file with `chmod 555` (hooks) or `chmod 444` (`settings-version`) because `block-write-commands.sh` permits only those two modes on protected paths — `chmod 644`, `chmod u+w` and `chattr` are all hard-denied to Claude, by design, so it can never widen its own access. The consequence is the one step 1 covers: read-only mode is *sticky* across updates and only you can clear it.
Expand Down
2 changes: 1 addition & 1 deletion global-settings/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.6.0
2.7.0
Loading
Loading