What happens
Regenerating chock-catalog against chock v0.7.0 (via tools/adopt_framework.py, see chock-catalog#46) produces duplicated hook entries in the compiled agent config:
.claude/settings.json — 14 PreToolUse entries where 7 should exist, and 2 SessionStart entries where 1 should exist
.cursor/hooks.json — 14 entries likewise
Exactly 2× in every case.
What does not happen — the part that narrows the search
It does not reproduce on a clean repo, and it does not accumulate. Reproduction attempt with chock==0.7.0 installed from PyPI into a fresh venv, a fresh git init, chock init ., two guard-shipping policies copied from the catalog (block-curl-pipe-sh, scan-secrets), then chock sync --repo . run four times:
after sync #1: .claude/settings.json {'SessionStart': 1, 'PreToolUse': 1} .cursor/hooks.json {'beforeShellExecution': 1}
after sync #2: .claude/settings.json {'SessionStart': 1, 'PreToolUse': 1} .cursor/hooks.json {'beforeShellExecution': 1}
after sync #3: .claude/settings.json {'SessionStart': 1, 'PreToolUse': 1} .cursor/hooks.json {'beforeShellExecution': 1}
after sync #4: .claude/settings.json {'SessionStart': 1, 'PreToolUse': 1} .cursor/hooks.json {'beforeShellExecution': 1}
Stable across repeated syncs. So this is not unbounded accumulation on re-sync, and the emitter's basic merge logic is not broadly broken.
Why that matters for severity
- No adopter is affected by a fresh install. A clean repo gets correct output.
- The symptom is a doubled entry, not a wrong one. A duplicated
PreToolUse entry means the guard runs twice on a tool call — wasteful, not unsound. It cannot allow something it should deny.
- Consequently this is not judged a hotfix. Fixing it in the next release rather than cutting a patch on top of
v0.7.0 seems right, but that is a maintainer call, not a conclusion of this report.
Where the trigger probably is
The clean-repo reproduction rules out the simple path. Two differences remain between it and the catalog, and the trigger is likely in one of them:
- Scale / pre-existing state. The catalog carries 25 policies and a committed
.claude/settings.json that predates this run; the repro started from nothing. A merge that appends rather than replaces when entries already exist would show exactly this 2× signature.
- The multi-tree plugin build. The catalog runs
chock plugin build across three published trees (base, compliance, agentic-security) in addition to chock sync. The repro ran sync only. If a per-tree pass re-emits into the same repo-level config, that would also produce 2×.
Distinguishing those two is probably a short experiment: re-run chock sync on a checkout of the catalog with .claude/settings.json deleted first, and see whether the duplication returns.
Provenance
Found by the worker performing the 0.7.0 catalog adoption. It did not hand-fix the generated files, per that repo's "never hand-edit generated output" rule — the artifacts in chock-catalog#46 contain the duplication as generated, so the evidence is preserved in that diff.
The clean-repo reproduction above was run separately to establish whether adopters are at risk.
What happens
Regenerating
chock-catalogagainst chockv0.7.0(viatools/adopt_framework.py, see chock-catalog#46) produces duplicated hook entries in the compiled agent config:.claude/settings.json— 14PreToolUseentries where 7 should exist, and 2SessionStartentries where 1 should exist.cursor/hooks.json— 14 entries likewiseExactly 2× in every case.
What does not happen — the part that narrows the search
It does not reproduce on a clean repo, and it does not accumulate. Reproduction attempt with
chock==0.7.0installed from PyPI into a fresh venv, a freshgit init,chock init ., two guard-shipping policies copied from the catalog (block-curl-pipe-sh,scan-secrets), thenchock sync --repo .run four times:Stable across repeated syncs. So this is not unbounded accumulation on re-sync, and the emitter's basic merge logic is not broadly broken.
Why that matters for severity
PreToolUseentry means the guard runs twice on a tool call — wasteful, not unsound. It cannot allow something it should deny.v0.7.0seems right, but that is a maintainer call, not a conclusion of this report.Where the trigger probably is
The clean-repo reproduction rules out the simple path. Two differences remain between it and the catalog, and the trigger is likely in one of them:
.claude/settings.jsonthat predates this run; the repro started from nothing. A merge that appends rather than replaces when entries already exist would show exactly this 2× signature.chock plugin buildacross three published trees (base,compliance,agentic-security) in addition tochock sync. The repro ransynconly. If a per-tree pass re-emits into the same repo-level config, that would also produce 2×.Distinguishing those two is probably a short experiment: re-run
chock syncon a checkout of the catalog with.claude/settings.jsondeleted first, and see whether the duplication returns.Provenance
Found by the worker performing the 0.7.0 catalog adoption. It did not hand-fix the generated files, per that repo's "never hand-edit generated output" rule — the artifacts in chock-catalog#46 contain the duplication as generated, so the evidence is preserved in that diff.
The clean-repo reproduction above was run separately to establish whether adopters are at risk.