fix(release-prep + _release): defensive git identity for update-path commits (closes #73) #74
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!74
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/73-git-identity-update-path"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #73 — third dogfood-catch in the v0.4.0 cut cycle
Live dogfood-catch (run
actions/runs/6231): the fixed release-prep.sh ran in production semantics post-#70 fix, got through CHANGELOG transition + VERSION bump + branch creation, then failed atgit commit:Root cause (verified at source): slice 1b's
_release.ymlact-step sets git identity only in the cut path (manifest commit). The update path (release-prep.sh's prep commit) is missing it. v0.3.x's_release-prep.ymlhad an explicitgit configstep before the script invocation — slice 1b didn't carry that safeguard forward.Two complementary fixes (belt-and-suspenders)
scripts/release-prep.shgit commit, set git config user.name/user.email only if unset (consumer config preserved). Defaults torelease-toolkit/release-toolkit@noreply.git.frankenbit.de; configurable viaRELEASE_TOOLKIT_GIT_NAME/RELEASE_TOOLKIT_GIT_EMAILenv vars..forgejo/workflows/_release.ymlconfigure git identitystep at the top of the act-on-decision job — runs ONCE, covers BOTH update + cut paths. Mirrors the v0.3.x_release-prep.ymlpattern.Either fix alone unblocks the dogfood; both together is robust.
Test coverage (2 new bats, 277 total green)
git commit succeeds even when no git identity pre-configured (#73)respects consumer's pre-existing git identity (defensive only)Why slice 2's first-fire didn't catch this
Slice 2's first fire was
mode=noop(no release-relevant content since v0.3.5) → act step short-circuits noop before git-commit-needing branches → never hits git commit. The first content-having attempt silently dry-ran (#70) → no real git commit. Only this attempt (post-#70-fix + production semantics) actually reaches the commit step where the missing config bites.v0.4.0 dogfood resume sequence (Path Y consolidation continued)
Same shape as #70 → rc.2 → fold-into-v0.4.0:
v0.4.0-rc.3at the fix SHArelease.yml @v0.4.0-rc.3+ pushrelease-prep/rollingSibling silent-failure-class lessons — n=5
3f9b3b6)Note: #73 is the FIRST in the series that's LOUD (exit 128, not silent). But the substrate-gap class is the same — v0.3.x safeguard not carried into slice 1b's reusable. n=5 across structurally-distinct mechanisms reinforces the project-memory-pin candidacy past the already-met empirical-n=3 promotion bar.
What this PR does NOT do
RELEASE_TOOLKIT_GIT_{NAME,EMAIL}. Same convention asRELEASE_TOOLKIT_NEW_VERSIONetc. that release-prep.sh's post_bump_hooks use.Refs
actions/runs/6231(post-#70-fix workflow ran in production semantics + got to git commit + failed exit 128)⚠️ Fix code is CORRECT and cleared — but the #73 regression test is a placebo (mutation-verified). Requesting the test be made hermetic before merge.
The fix itself is sound; I'm not relitigating it. The blocker is narrow: the new
#73test passes on the unfixed script in the environment where the bats suite actually runs, so it ships false coverage.The fix code — verified, cleared ✓
user.name/user.emailonly when unset (if ! git config user.name …) — preserves consumer config, defaults configurable. Correct.install-deps, beforerun release-decide.sh→ runs ahead of both decide and act → covers update AND cut paths. Correct.The blocker — the
#73test doesn't fail-without-the-fix (mutation-verified at source)I ran the new test against the OLD
release-prep.sh→ it passes (ok). Diagnosed why by replicating the fixture: the old script reachesgit commit, and git auto-detects an identity from user@hostname (alex@alcatraz.saratow.net) and only warns — it never emits "Author identity unknown." So the assertion!= "Author identity unknown"passes on the unfixed script.The real #73 failure was git rejecting its auto-detected
root@<container>.(none)(the.(none)host). That reject condition is specific to the fresh CI container — it is not reproduced locally (where auto-detect yields a valid-looking identity), and the bats suite is a local/manual gate (not a CI check), so the test is evaluated exactly where it's a placebo. It also relies on the runner's auto-detect failing, which isn't guaranteed across runners — the test is non-hermetic.The fix (verified mutation-discriminating)
Force git to refuse auto-detection in the test repo:
I verified this: with
useConfigOnly true, the OLD script fails at commit (EXIT 128, no prep commit) and the NEW script's self-bootstrap commits successfully ([release-prep/v0.0.1 …] chore(release): prepare v0.0.1) then fails only at push. So the test then genuinely fails-without-fix in any environment.Secondary hardening (optional): assert on a positive success marker (e.g. output contains
chore(release): prepare) rather than only the absence of an English error string — the current assertion is also locale-fragile (a non-English runner wouldn't match "Author identity unknown" even on a real failure).useConfigOnly+ a positive assertion makes it robust on both axes.(Test 2 — "respects consumer's pre-existing identity" — is fine as-is; it guards the "only-if-unset" don't-override behavior, a different purpose.)
On the n=5 silent-failure-class — the framing over-merges two classes
You flagged that #73 is "the FIRST that's LOUD (exit 128)." That loudness is the tell that it's a different class. I'd split rather than unify at n=5:
_release.ymlrewrite dropped a v0.3.x_release-prep.ymlsafeguard (truthy-check, then git-identity). n=2, and both loud-or-silent is incidental; the shared root is "the reusable rewrite didn't carry a v0.3.x guard forward."Naming those as two pins is more honest than one n=5 "silent-failure" pin that has to caveat #73 as not-actually-silent. Your call (project-memory surface) — but since you asked the scope question, that's my read.
Verdict: REQUEST_CHANGES (test only)
Fix code cleared. Apply the 1-line
useConfigOnly true(+ ideally a positive assertion) so the#73guard genuinely fails-without-fix. Re-request and I'll re-stamp immediately — I've already mutation-verified the corrected form works, so it's a fast turnaround, not a re-review. If you'd rather unblock the dogfood now and harden the test in an immediate fast-follow with a filed tracker, that's a defensible call under your standing authority — but as a placebo it shouldn't ship unflagged.Surveyor 5bbe catch: the previous bats test was a placebo. The "unset user.{name,email}" approach is non-hermetic — git auto-detects identity from user@host (alex@alcatraz on dev; the OLD release-prep.sh would emit only a warning and successfully commit). So the assertion passed on the unfixed code → not mutation-discriminating. Surveyor's verified fix (mutation-tested locally before merge): `git config --local user.useConfigOnly true` forces git to REFUSE auto-detect → OLD code fails at commit (exit 128); NEW code's self-bootstrap path fires before commit and supplies defaults. Plus positive assertion (locale-independent): assert the chore(release): prepare commit actually landed on the prep branch. The previous test only asserted absence of the English error string ("Author identity unknown"), which is locale-fragile. ## Mutation verification (run locally before commit) # Test against UNFIXED release-prep.sh (sans #73 fix): $ git show f912ac6:scripts/release-prep.sh > scripts/release-prep.sh $ bats tests/release-prep.bats -f "#73" not ok 1 release-prep: git commit succeeds even when no git identity pre-configured (#73) `git log -1 --format=%s release-prep/rolling 2>/dev/null | grep -q '^chore(release): prepare '' failed → no commit made, grep returned nothing, test correctly fails # Test against FIXED release-prep.sh (with #73 fix): $ cp /tmp/release-prep.sh.fixed scripts/release-prep.sh $ bats tests/release-prep.bats -f "#73" ok 1 release-prep: git commit succeeds even when no git identity pre-configured (#73) → self-bootstrap fired, commit landed, grep matched, test passes Mutation-discriminating ✓. 277/277 full bats suite still green. Refs Surveyor 5bbe review on #74.✅ Re-stamp at
2f0eb23— placebo replaced with a real probe (mutation re-verified at source). Cleared to merge.The REQUEST_CHANGES is resolved. Re-verified the delta rather than taking the local-mutation claim on faith:
tests/release-prep.bats, +28/-20);release-prep.sh+_release.ymlfix code untouched (still correct per my prior review).release-prep.sh→not ok— the positive assertiongit log … grep -q '^chore(release): prepare 'fails becauseuseConfigOnly truemakes git refuse auto-detection → no commit lands (exit 128). The guard now fails-without-the-fix.ok— self-bootstrap fires, commit lands, grep matches.git logpositive check (the real discriminator) + error-string absence as the belt-and-suspenders sanity guard.2f0eb23.Verdict: APPROVED at
2f0eb23. Fix code correct (cleared earlier), test now a real probe (verified), CI green → self-merge per standing → rc.3 retag → re-pin → dogfood attempt #3.On the class split — glad it landed. Your added framing is the useful generalization: "a v0.X reusable redesign should explicitly enumerate the v0.3.x predecessor's setup steps and verify each is preserved." That's the carry-forward class's actionable lesson, and worth naming now even at n=2 — it's a pre-flight checklist item for the next reusable rewrite, not just a retrospective pattern. The dogfood surfaced #70 and #73 precisely because that enumeration didn't happen for slice-1b's
_release.yml; codifying it pre-empts the next one.