Problem
buildScripts/util/check-chore-sync.mjs refuses any commit that stages resources/content/** on a branch not prefixed chore/sync-* / agent/sync-*. It reads the staged set with a single call and has no merge awareness:
const output = execSync('git diff --cached --name-only', { cwd: gitRoot, encoding: 'utf-8' }).trim();During a merge commit, every file the merge brings in is staged. So git merge origin/dev on a feature branch stages dev's data-sync commits — and the guard reads that as the feature branch authoring sync data.
The guard cannot distinguish the thing it exists to prevent (authoring sync data on a feature branch) from a normal, unavoidable operation (merging dev, which carries the pipeline's commits).
Why this is blocking, not cosmetic
It compounds with the force-push allowlist, and the two together close both ways of refreshing a branch:
.claude/settings.template.json:30 allows git push --force-with-lease origin agent/*. Branches under agent/* can therefore rebase — a rebase replays only the author's own commits, never stages dev's sync files, and never trips this guard.
- The live maintainer convention is
<name>/<ticket>-<slug> — verified against origin: ada/*, grace/*, vega/*. These are not covered by that allowlist, so they cannot rebase.
- Their only remaining option is
git merge origin/dev — which this guard blocks.
Net: a maintainer on a name-prefixed branch cannot bring their branch up to date with dev at all. The remaining exits are --no-verify (banned — it also skips the AiConfig test-mutation safety lint) or unstaging the sync files, which makes the merge commit revert dev's sync data when it lands.
dev takes roughly 2 data-sync commits every 6 hours, so any branch older than the last sync hits this. It is not an edge case; it is the default state of any branch more than an hour old.
Reproduction (exact, observed)
On grace/15272-throttle-producer (base 0a33032ffb), after #15280 merged to dev:
git merge --no-edit origin/dev # resolve the 2 real code conflicts, stage them
git commit --no-edit
→ Error: Sync-data leakage detected.
Branch 'grace/15272-throttle-producer' is not a designated data-sync branch
The following data files are staged for commit:
- resources/content/.sync-metadata.json
- resources/content/_index.json
- resources/content/discussions/chunk-1/discussion-15173.md
… (17 more, all authored by the sync pipeline on dev)None of those files were touched by the branch. They arrived with the merge.
Prior-art sweep
rg -n "MERGE_HEAD" buildScripts/ .husky/ → exit 1, zero hits. No guard in the hook layer is merge-aware; there is no existing escape to reuse or mirror.
- Tracker sweep of the 60 latest open issues: the existing hook tickets (#14420, #14921) are both about
laneStateStopHook, unrelated to this guard. No duplicate.
Acceptance Criteria
Out of scope
The force-push allowlist gap (agent/* vs the live <name>/* convention) is the other half of this trap and wants its own decision — it is an operator-owned permissions question, not a script fix. Filed here only as the reason this guard is load-bearing rather than annoying. Fixing the guard is sufficient to unblock merging; widening the allowlist would additionally restore rebasing.
Evidence
buildScripts/util/check-chore-sync.mjs:50 — the merge-blind staged-file read.
.claude/settings.template.json:30 — git push --force-with-lease origin agent/*.
git branch -r — the live convention is ada/*, grace/*, vega/*.
git log --oneline origin/dev --since="6 hours ago" | rg -c "chore\(data\)" → 2.
Surfaced while trying to land #15272's RA-1 roster join on #15281 after #15280 merged: the work is complete and green (502/502) but unpublishable, because the reviewer-sanctioned stack needs a force-push the harness denies and the merge alternative trips this guard.
Problem
buildScripts/util/check-chore-sync.mjsrefuses any commit that stagesresources/content/**on a branch not prefixedchore/sync-*/agent/sync-*. It reads the staged set with a single call and has no merge awareness:// buildScripts/util/check-chore-sync.mjs:50 const output = execSync('git diff --cached --name-only', { cwd: gitRoot, encoding: 'utf-8' }).trim();During a merge commit, every file the merge brings in is staged. So
git merge origin/devon a feature branch stages dev's data-sync commits — and the guard reads that as the feature branch authoring sync data.The guard cannot distinguish the thing it exists to prevent (authoring sync data on a feature branch) from a normal, unavoidable operation (merging dev, which carries the pipeline's commits).
Why this is blocking, not cosmetic
It compounds with the force-push allowlist, and the two together close both ways of refreshing a branch:
.claude/settings.template.json:30allowsgit push --force-with-lease origin agent/*. Branches underagent/*can therefore rebase — a rebase replays only the author's own commits, never stages dev's sync files, and never trips this guard.<name>/<ticket>-<slug>— verified againstorigin:ada/*,grace/*,vega/*. These are not covered by that allowlist, so they cannot rebase.git merge origin/dev— which this guard blocks.Net: a maintainer on a name-prefixed branch cannot bring their branch up to date with dev at all. The remaining exits are
--no-verify(banned — it also skips the AiConfig test-mutation safety lint) or unstaging the sync files, which makes the merge commit revert dev's sync data when it lands.devtakes roughly 2 data-sync commits every 6 hours, so any branch older than the last sync hits this. It is not an edge case; it is the default state of any branch more than an hour old.Reproduction (exact, observed)
On
grace/15272-throttle-producer(base0a33032ffb), after #15280 merged to dev:git merge --no-edit origin/dev # resolve the 2 real code conflicts, stage them git commit --no-edit → Error: Sync-data leakage detected. Branch 'grace/15272-throttle-producer' is not a designated data-sync branch The following data files are staged for commit: - resources/content/.sync-metadata.json - resources/content/_index.json - resources/content/discussions/chunk-1/discussion-15173.md … (17 more, all authored by the sync pipeline on dev)None of those files were touched by the branch. They arrived with the merge.
Prior-art sweep
rg -n "MERGE_HEAD" buildScripts/ .husky/→ exit 1, zero hits. No guard in the hook layer is merge-aware; there is no existing escape to reuse or mirror.laneStateStopHook, unrelated to this guard. No duplicate.Acceptance Criteria
.git/MERGE_HEAD, orgit rev-parse -q --verify MERGE_HEAD) and exit 0 before the branch check. A merge does not author sync data; the pipeline's commits are already dev's history.resources/content/**on a non-sync branch — the original protection is intact and proven by a test that would catch its removal.origin/devinto a name-prefixed branch and committing, not only by unit assertion.Out of scope
The force-push allowlist gap (
agent/*vs the live<name>/*convention) is the other half of this trap and wants its own decision — it is an operator-owned permissions question, not a script fix. Filed here only as the reason this guard is load-bearing rather than annoying. Fixing the guard is sufficient to unblock merging; widening the allowlist would additionally restore rebasing.Evidence
buildScripts/util/check-chore-sync.mjs:50— the merge-blind staged-file read..claude/settings.template.json:30—git push --force-with-lease origin agent/*.git branch -r— the live convention isada/*,grace/*,vega/*.git log --oneline origin/dev --since="6 hours ago" | rg -c "chore\(data\)"→ 2.Surfaced while trying to land #15272's RA-1 roster join on #15281 after #15280 merged: the work is complete and green (502/502) but unpublishable, because the reviewer-sanctioned stack needs a force-push the harness denies and the merge alternative trips this guard.