Skip to content

fix(#106): CHANGELOG fallback in drift hook for squash-merged forks#129

Merged
atlas-apex merged 2 commits into
devfrom
fix/GH-106-drift-hook-squash-merge
Apr 25, 2026
Merged

fix(#106): CHANGELOG fallback in drift hook for squash-merged forks#129
atlas-apex merged 2 commits into
devfrom
fix/GH-106-drift-hook-squash-merge

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

Fixes the bug from #106: the v1.1.0 tag-based drift hook keeps firing on forks that sync via GitHub's default squash-merge. The squash collapses the upstream-tag commit into a synthetic SHA, the tag becomes unreachable from the fork's main, and git tag --merged main returns empty — so the hook says "vX.Y.Z available" forever.

Adds a CHANGELOG-content fallback that fires only when the primary tag-reachability check fails. If the fork's main has a ## [X.Y.Z] heading matching the upstream tag's version, the release is treated as absorbed and the banner stays silent.

  • changelog_has_version() helper does a tolerant grep against ${DEFAULT_BRANCH}:CHANGELOG.md. Strips leading v from the tag for the heading match.
  • Two fallback gates: (1) LOCAL_TAG empty (first sync, squash-merged); (2) UPSTREAM_TAG strictly newer than LOCAL_TAG (intermediate sync, squash-merged).
  • Merge-commit and rebase paths unchanged — the primary tag check still works for them, the fallback only kicks in on its failure.
  • Records the strategy update in docs/agdr/AgDR-0008-changelog-fallback-for-squash-merged-forks.md (extends AgDR-0005's original tag-based-drift design).

Why this approach

The ticket listed four options:

  • (A) Manual fork-tag convention — relies on humans/skill to remember; same failure mode if forgotten
  • (B) Pure CHANGELOG check — discards the working tag-reachability fast path
  • (C) Hybrid (chosen) — preserves working merge-commit/rebase paths; adds recovery for squash-merge
  • (D) New committed state file — most accurate but new file convention + migration cost

(C) is in-place: zero changes to the /update skill, zero new files outside the AgDR, zero migration story for existing forks. The CHANGELOG format dependency is acceptable because apexyard owns the CHANGELOG and the format is stable from v1.1.0 onward. AgDR-0008 documents the option table.

Testing

5 cases in .claude/hooks/tests/test_check_upstream_drift.sh. Each builds a tiny upstream + fork pair under $TMPDIR, simulates a specific merge mode, runs the hook, asserts banner / silence:

$ bash .claude/hooks/tests/test_check_upstream_drift.sh
PASS [squash-merge fork caught up to v1.1.0 → silent (FIXED by #106)]
PASS [merge-commit fork caught up to v1.1.0 → silent]
PASS [fork stopped at v1.0.0 → banner fires]
PASS [fork has its own newer tag → silent]
PASS [squash-merge but no CHANGELOG on fork → banner fires (no false silence)]
PASS: 5   FAIL: 0

The fifth case is the critical "fail-open" check — a fork that squash-merges but doesn't keep CHANGELOG.md should NOT be silenced incorrectly. Regression-protected.

Acceptance criteria (from #106)

  • After a squash-merged sync PR lands on a fork, the SessionStart banner goes silent
  • Fix doesn't regress merge-commit / rebase workflows (covered by case 2)
  • CHANGELOG + docs updated — AgDR-0008 added; CHANGELOG.md update lands with the v1.2.0 release entry
  • Regression test / verification recipe — tests/test_check_upstream_drift.sh

Scope — what this does NOT do

  • Does not modify the /update skill. Existing flow works as-is.
  • Does not introduce a new committed state file (Option D — deferred; revisit if the CHANGELOG-format dependency turns out to bite).
  • Does not retroactively fix forks already showing the misfire — they go silent the next time their fork's CHANGELOG has the upstream version's heading (typically: the next session after they manually drop a ## [X.Y.Z] line into their fork's CHANGELOG, or after the next /update sync).

Glossary

Term Definition
Squash-merge GitHub's default "Squash and merge" mode — collapses N commits in a PR into one synthetic commit on the target branch. Severs ancestor reachability to the source commits.
Tag reachability git tag --merged BRANCH — does the tag's target SHA appear in BRANCH's ancestry? Reliable for merge-commit / rebase, not for squash-merge.
CHANGELOG fallback Content-based "did we absorb this release" check via ^##\s+\[X.Y.Z\] heading grep. Tolerant secondary signal.
Fail-open If the fallback can't confirm absorption, fire the banner — don't silence on missing evidence. (Case 5 covers this.)

Closes #106

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants