build: no gate asserts that tool-owned paths stay out of autofixing formatter scope #102

Open
opened 2026-08-14 22:04:04 +00:00 by Claude · 1 comment
Collaborator

Surfaced while fixing an apm audit --ci failure on branch refactor/trim-skills-agents-context (see that PR).

The failure mode

.claude/settings.json is apm-owned per ADR-0018/0019 — its committed content must be exactly what apm writes. But pretty-format-json --autofix in .pre-commit-config.yaml sorts JSON object keys unless --no-sort-keys is passed, and its exclude: pattern listed fifteen generated manifests without listing this one.

So from commit 2e395a4 onward — the commit that first wrote a SessionStart hook entry into that file — apm's insertion-ordered output was silently re-sorted on the way into every commit. apm replayed the install, produced its own key order, and reported drift against a file no human had touched.

Content was byte-identical throughout. Only key order differed.

Why this failure mode is unusually bad

The drift never appears in git diff. The file matched HEAD exactly, so every instinct says nothing changed here. The real diff exists only between the formatter's input and its output, which nothing stores.

The fix is self-undoing. Correcting the file alone does not work — the hook re-breaks it during staging. The exclude has to land in the same commit as the corrected content.

The structural gap

.claude/settings.json was the sixteenth exclude on that hook, and nothing prevents a seventeenth. This repo gates several drift classes deterministically:

  • check-plugin-content-sync
  • check-marketplace-mirror-sync
  • check-vale-style-sync
  • check-scope-walkup-sync
  • check-executables-allow-sync

There is no equivalent gate asserting that tool-owned paths stay out of autofixing hook scope. Any future path a tool claims ownership of — a new apm-deployed file, a generated manifest, a compiled artifact — silently acquires this bug unless someone remembers to add an exclude at the moment ownership is declared.

Possible shapes (not a decision)

  • A check-formatter-scope-sync.sh pre-push hook that derives the set of tool-owned paths (apm deployment ledger, generated-manifest lists) and asserts each one is excluded from every autofixing hook in .pre-commit-config.yaml.
  • Or invert it: pass --no-sort-keys globally and make key-sorting opt-in per path. Smaller mechanism, but it reformats every other JSON file in the repo, so it needs its own migration.
  • Or accept it as a documented convention with no gate — but this repo's own precedent is that an ungated convention is invisible (ADR-0013).

This is arguably ADR-sized, since it is a decision about how far deterministic drift gating extends.

Surfaced while fixing an `apm audit --ci` failure on branch `refactor/trim-skills-agents-context` (see that PR). ## The failure mode `.claude/settings.json` is apm-owned per ADR-0018/0019 — its committed content must be exactly what apm writes. But `pretty-format-json --autofix` in `.pre-commit-config.yaml` sorts JSON object keys unless `--no-sort-keys` is passed, and its `exclude:` pattern listed fifteen generated manifests without listing this one. So from commit `2e395a4` onward — the commit that first wrote a `SessionStart` hook entry into that file — apm's insertion-ordered output was silently re-sorted on the way into every commit. apm replayed the install, produced its own key order, and reported drift against a file no human had touched. Content was byte-identical throughout. Only key order differed. ## Why this failure mode is unusually bad **The drift never appears in `git diff`.** The file matched `HEAD` exactly, so every instinct says nothing changed here. The real diff exists only between the formatter's input and its output, which nothing stores. **The fix is self-undoing.** Correcting the file alone does not work — the hook re-breaks it during staging. The exclude has to land in the same commit as the corrected content. ## The structural gap `.claude/settings.json` was the **sixteenth** exclude on that hook, and nothing prevents a seventeenth. This repo gates several drift classes deterministically: - `check-plugin-content-sync` - `check-marketplace-mirror-sync` - `check-vale-style-sync` - `check-scope-walkup-sync` - `check-executables-allow-sync` There is no equivalent gate asserting that **tool-owned paths stay out of autofixing hook scope**. Any future path a tool claims ownership of — a new apm-deployed file, a generated manifest, a compiled artifact — silently acquires this bug unless someone remembers to add an exclude at the moment ownership is declared. ## Possible shapes (not a decision) - A `check-formatter-scope-sync.sh` pre-push hook that derives the set of tool-owned paths (apm deployment ledger, generated-manifest lists) and asserts each one is excluded from every autofixing hook in `.pre-commit-config.yaml`. - Or invert it: pass `--no-sort-keys` globally and make key-sorting opt-in per path. Smaller mechanism, but it reformats every other JSON file in the repo, so it needs its own migration. - Or accept it as a documented convention with no gate — but this repo's own precedent is that an ungated convention is invisible (ADR-0013). This is arguably ADR-sized, since it is a decision about how far deterministic drift gating extends.
Claude added the Kind/Enhancement
Priority
Medium
3
labels 2026-08-14 22:04:04 +00:00
Claude added this to the Tooling milestone 2026-08-14 22:04:24 +00:00
Author
Collaborator

Automated note from the PR #135 review follow-up. This issue's own conclusion, state and labels are untouched — this comment only records which of the gates it cites still exist, so the issue stays actionable.

Three of the five precedent gates are gone

Still present:

  • check-scope-walkup-sync
  • check-executables-allow-sync

Deleted by PR #135's ADR-0024 / ADR-0025 work:

  • check-plugin-content-sync
  • check-marketplace-mirror-sync
  • check-vale-style-sync

The argument this issue makes from precedent still stands on the two surviving gates, but three of the five examples will not resolve any more and should be re-pointed or dropped when the issue is picked up.

Nothing else in this issue was re-validated.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01NwD8Egs5r4ndqeFLmhusX2

Automated note from the PR #135 review follow-up. This issue's own conclusion, state and labels are untouched — this comment only records which of the gates it cites still exist, so the issue stays actionable. **Three of the five precedent gates are gone** Still present: - `check-scope-walkup-sync` - `check-executables-allow-sync` Deleted by PR #135's ADR-0024 / ADR-0025 work: - `check-plugin-content-sync` - `check-marketplace-mirror-sync` - `check-vale-style-sync` The argument this issue makes from precedent still stands on the two surviving gates, but three of the five examples will not resolve any more and should be re-pointed or dropped when the issue is picked up. Nothing else in this issue was re-validated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NwD8Egs5r4ndqeFLmhusX2
Sign in to join this conversation.