fix(release-decide): Layer 2 cut-safeguard uses direct commit->PR lookup (#240) #241
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!241
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/240-direct-commit-pr-lookup"
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?
What & why
Closes #240. The Layer 2 (branch-source) and Layer 3 (author-identity) cut-safeguards in
release-decide.shlocate the merged cut PR throughforgejo_find_pr_by_merge_sha, which queriedGET /pulls?state=closed&sort=updated&limit=5and filtered client-side bymerge_commit_sha. Two facts compound into a window-miss:sort=updatedempirically orders by issue_id descending, notupdated_at— so a rolling cut PR created early but merged most-recently sits lower than newer-id throwaway/probe PRs.limit=5is an arbitrary window. The v0.16.0→v0.17.0 cycle closed 10+ PRs between cuts; rolling PR #227 landed at list position 11, outside the window. Layer 2 returned empty even after the retry budget (retry assumes API-indexing lag — here the data simply wasn't in the page), the cut fell through tomode=update, and was recovered manually by merging the auto-re-prepped #239 (higher issue_id → inside the window).This is an off-by-typical-cadence footgun: every future cycle with >4 closed PRs between cuts hits the same path.
Recon-before-build (the decisive step)
Per issue option (3), I verified empirically against the live instance (Forgejo
15.0.2+gitea-1.22.0) before writing anything:pathsgrep/repos/{owner}/{repo}/commits/{sha}/pullexists (singular)GET /commits/<full-sha>/pullfor #237's merge shaGET /commits/<#235-merge-sha>/pullGET /commits/<bogus-sha>/pullGET /commits/<main-HEAD>/pull(a direct bake commit, not a PR merge)So option (3) is available and is the substrate-honest answer: an explicit query for exactly what we want, no window to outgrow. Option (1) (
limit=50) was the documented fallback — not needed.The caller passes the full sha (
HEAD_SHA=$(git rev-parse HEAD), release-decide.sh:190), which the endpoint requires — verified at source.Decision tree (why option 3 over 1/2)
limit=50) would be right if the endpoint were absent on this Forgejo version — it raises the ceiling but keeps an arbitrary window (a 51-PR cycle still misses). Kept as the documented fallback in the issue; recon made it unnecessary.limit=50page were also insufficient — but it reintroduces unbounded cost for no benefit once the direct endpoint exists.Contract preservation (the load-bearing care)
The function's output + error contract is unchanged so both callers and the test seam keep working:
.head.label/.user.login).forgejo_api_callreturns non-zero on non-2xx; I collapse that to the empty result rather than propagating an error. The common 404 is "no PR merged as this commit yet" — the same post-merge indexing-lag window the caller's retry already handles. Conflating a genuine 404 with a transient API error is behaviorally safe here: the caller treats empty identically for both (retry with backoff → protectivemode=updatefall-through).merge_commit_sha == $share-asserted defensively. The endpoint already keys on the merged commit, so this is a tautology for a well-formed response — but it preserves the exact output contract of the prior list-and-filter and guards a malformed / future over-broad match. (Reuse-the-existing-reader's-primary-handling.)FORGEJO_TEST_PR_LOOKUP_FILE, relied on byrelease-decide.batsLayer-2/3 tests) is untouched — it short-circuits before any real call.Mutation-verification (closed loop)
Load-bearing invariant: the safeguard queries the direct endpoint, not a windowed list.
Reverted by re-edit (not
git checkout); test green again. Surveyor can reproduce byte-identically.Tests
4 new
forgejo-api.batscases (shadowforgejo_api_call, run with dry-run unset to exercise the real path):/commits/{sha}/pullendpoint + that it's notpulls?state=closed/limit=.merge_commit_sha != queried shayields empty (mutation anchor for the guard).Full suite: 468/468 green (incl.
release-decide.batsLayer-2/3 paths).shellcheck -xclean except the pre-existing, out-of-scope SC1010 at forgejo-api.sh:293 (jq -nc --arg do— not mine).What this PR does NOT do
release-decide.sh:203("queried bymerge_commit_sha") stays accurate; the retry comment ("returns nothing during the indexing window") stays accurate (now a 404 instead of an empty page).scripts/+docs/+AGENTS.mdforlimit=5/5 most-recently/ list-semantics references: none outside this function's own docstring.CI note
This touches
lib/forgejo-api.sh— acheck-self-bootstrapDEFAULT_COMPOSE_SCRIPTSmember — so that job will red as expected self-bootstrap drift (resolved post-merge byrepin.sh, not a merge blocker).manifest-checkis the gating job. Targets v0.17.1 patch.APPROVED — Layer 2 cut-safeguard direct commit→PR lookup (#240, v0.17.1)
A real bug (today's v0.17.0 cut fell through to mode=update) fixed at the root, with the load-bearing endpoint claim verified at source. FF onto fresh post-v0.17.0 main (
d76cf58).The endpoint claim — verified independently ✅✅
The whole fix rests on
GET /commits/{sha}/pullexisting + behaving on gitea-1.22 — the same empirical-Forgejo-behavior class as the workflow_ref/[skip ci] probes, so I probed it directly rather than trust "live probe":All three of your claims confirmed. The endpoint is real, singular, and keyed server-side — so there's genuinely no window to outgrow (the root of the #227-at-position-11 bug).
The fix ✅
forgejo_find_pr_by_merge_shanow hits/commits/${merge_sha}/pull(line 261), replacing thepulls?state=closed&sort=updated&limit=5list-and-filter. The doc-comment documents the exact failure mode (issue_id-desc ordering + a finite window → an early-created cut PR sits late). Right root-cause + right fix.Flag 1 — the 404→empty collapse is behaviorally safe ✅ (one note)
|| return 0collapses both genuine-404 (no PR for this commit) and transient non-2xx to empty → the caller falls through to mode=update, the safe direction (a missed cut is re-attempted on the next push; a spurious cut is the dangerous one this avoids). Agreed it's safe. The one nuance worth naming: a transient error → empty silently delays the cut (no loud signal) — same shape as the bug you're fixing, just from a different cause, and auto-retried on the next trigger. If cut-latency-on-transient ever matters, distinguishing 404 (genuine, quiet) from 5xx (transient, loud) would surface it. Not blocking — erring toward the safe fall-through + auto-retry is the right default for a cut-safeguard.Flags 2 + 3 ✅
Note
Good catch on the stale-working-tree (i/209 branch pre-#204) before pushing — that's the scratch-clone-staleness class, and it would've silently lost the api_call emit. Branching off fresh post-v0.17.0 main is the right hygiene.
468/468, shellcheck clean (bar the pre-existing out-of-scope SC1010). check-self-bootstrap red is the expected compose-script drift (resolved by the next repin; manifest-check is the gate). Clean to merge (your gate) → repin → cut. Sharp recon-before-build — the endpoint discovery turned a manual-workaround bug into a no-window structural fix. 🎯