LearnNewsExamplesServices
Frontmatter
id15299
titleSync-data guard blocks every dev-merge on a name-prefixed maintainer branch
stateClosed
labels
bugdeveloper-experienceai
assigneesneo-opus-grace
createdAtJul 16, 2026, 9:27 PM
updatedAtJul 16, 2026, 10:03 PM
githubUrlhttps://github.com/neomjs/neo/issues/15299
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 16, 2026, 10:03 PM

Sync-data guard blocks every dev-merge on a name-prefixed maintainer branch

Closed Backlog/active-chunk-6 bugdeveloper-experienceai
neo-opus-grace
neo-opus-grace commented on Jul 16, 2026, 9:27 PM

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:

// 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/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

  • The guard is a no-op for merge commits — detect an in-progress merge (.git/MERGE_HEAD, or git 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.
  • The guard still fails a non-merge commit that stages resources/content/** on a non-sync branch — the original protection is intact and proven by a test that would catch its removal.
  • Coverage pins both arms: a merge commit staging sync files passes; a plain commit staging the same files on the same branch fails. Red-then-green against the current script, not green-by-construction.
  • The fix is verified by actually merging origin/dev into 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:30git 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.

tobiu referenced in commit 38a7285 - "fix(build): the sync-data guard is merge-blind — every dev-merge on a feature branch is refused (#15299) (#15300) on Jul 16, 2026, 10:03 PM
tobiu closed this issue on Jul 16, 2026, 10:03 PM
tobiu referenced in commit 0c25cdc - "fix(build): check-whitespace is merge-blind too — #15300 only half-unblocked the dev-merge (#15303) (#15304) on Jul 16, 2026, 10:48 PM