Skip to content

[Bug] release workflow — dev/main squash divergence compounds; needs main→dev sync after each release #403

@atlas-apex

Description

@atlas-apex

Bug Scenario

Given the apexyard framework uses a release-cut branch model (devmain via release PRs per AgDR-0007)
When a release PR is squash-merged into main, the squash commit on main has a different SHA than the equivalent dev-branch history
Then dev accumulates "phantom" un-synced commits that look like they're ahead of main but were actually already shipped via the squash. A future dev → main release PR will conflict with the squashed deltas already on main.
Expected every release PR is followed by a deterministic main→dev sync PR that reconciles the squash divergence within the same release cycle.

Repro Steps

git fetch upstream main dev --tags
git log upstream/dev..upstream/main --oneline

Currently returns 5 squash commits on main that are NOT on dev:

eb41cfe release(#401): v2.0.2 (#402)
b29b266 fix(#399): site/ — GA4 + consent banner on all 4 pages (#400)
189f86a release(#395): v2.0.1 (#396)
d1c2115 release(#391): v2.0.0 (#392)
15516a8 release(#278): v1.3.0 (#279)
52f220c release(#160): v1.2.0 (#172)

(Three of these — v1.2.0, v1.3.0, v2.0.0 — date back months. v2.0.1 + v2.0.2 added two more, plus the v2.0.2 underlying fix b29b266 because the v2.0.1 cherry-pick path landed it directly on main, not dev.)

Inverse direction (git log upstream/main..upstream/dev) shows 124 commits on dev that aren't on main — these are the legitimate "dev is ahead" deltas that will land in the next release.

Severity

P2 — not blocking any current work, but compounds with every release. Each unaddressed release cycle adds one more divergence point. The next dev → main release PR will conflict at a higher rate the longer this is left; we already paid the cost on v2.0.0 (99-conflict mega-merge) and worked around it on v2.0.1 + v2.0.2 by branching releases directly from main.

Mitigation

Workaround that's been used recently: branch release PRs directly from main (cherry-pick path) instead of dev, sidestepping the divergence cost per-release. Works for single-commit PATCH releases but doesn't scale — any release containing >1 commit that's only on dev can't use the workaround.

Real fix candidates (pick one):

  1. /release-sync skill — automation that runs as the final step of /release: after the release PR merges to main, file a sync-back PR from main into dev that just merges main with -X theirs (dev wins everywhere the squash didn't change content, the squash commits become merge ancestors of dev). One PR per release.
  2. Branch-protect main with a "must be ancestor of dev" rule — won't work for squash-merge releases by design; mostly a non-starter.
  3. Switch from squash to merge-commit on release PRs — preserves dev's commit history into main, eliminates divergence at the source. Costs: main's log gets noisier; release PR ergonomics change. Worth considering but a bigger framework decision.

Option 1 is the lowest-friction; recommend filing as a separate [Feature] /release-sync skill ticket and then closing this bug when that ships.

Investigation Notes

The divergence pattern is:

dev:      A — B — C — D (4 daily commits)
                       \
main:     ... — (squash:A+B+C+D as one commit X) ... — tag v1.X.0

After release: dev still has A, B, C, D as separate commits; main has only X. They share NO commits between them since the last fully-synced point. A future PR dev → main containing A — B — C — D — E (new commit E) will conflict on everything in A — D's diffs that X also touched.

The sync-back direction is much friendlier: git merge -X theirs from main into dev tells git "where there's a conflict, the dev version wins" — which is correct because dev already has those changes via the unsquashed commits. The merge commit then makes the squash commits an ancestor of dev, so future dev → main PRs only see the genuinely-new commits.

Glossary

Term Definition
Release-cut branch model apexyard-framework convention (AgDR-0007): daily PRs land on dev; main only receives release PRs from dev (tagged with semver). Framework-only — managed projects stay trunk-based.
Squash divergence When a release PR is squash-merged to main, the resulting commit has a different SHA than the equivalent dev history. dev still carries the unsquashed commits unless a sync-back PR is filed.
Cherry-pick release path Workaround used for v2.0.1 + v2.0.2: branch the release from upstream/main (not dev), pick or apply the single fix, PR to main. Only works for releases containing 1 commit.
Merge -X theirs Git merge strategy option that resolves conflicts in favour of "their" side — used on main→dev syncs where dev already has the un-squashed equivalents and "wins" by default.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — plan-worthy, not urgent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions