bug(forgejo-client): the runner envelope total_count is decoded and discarded, so a repeated-page forge looks like a large one #1385
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#1385
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
The gitea runner envelope carries
total_countand our decoder throws it away, so a forge that repeats pages is indistinguishable from a repository with a lot of runners when the pagination cap refuses.Measured, same file, same envelope shape
One reader treats the count as load-bearing; its sibling drops it.
What a reader would notice
An adopter whose forge ignores
pagegetshit MaxPages=N cap on a full page— which reads as "you have a lot of runners". It is the same message a genuinely large repository gets. The envelope already carries the number that separates them, on page 1, and we do not look at it.Scope — this is NOT a second terminator
⚠️ The empty page stays the only thing that ends the walk. This is a fail-CLOSED cross-check layered beside it:
Nothing about when the walk stops changes. Only what the refusal is able to say.
AC
decodeActionScopePagesurfacestotal_countwhere the envelope carries it, and tolerates its absence on the bare-array forge — and on an envelope that OMITS it, which is a THIRD state:decodeEnvelopeTotalCountreturnsnilfor absent, refuses present-but-unreadable. Pinned byTestAnEnvelopeWithoutTotalCountIsTolerated.INDISTINGUISHABLErather than picking the reassuring one.983510a1; every one reddens, none inert. Control isTestALargeListStillRefusesAsALargeList.TestTotalCountNeverEndsTheWalkasserts on the REQUEST LOG, not the returned rows:total_count=2is satisfied exactly by page one, so a walk that stopped there would return the right rows for the wrong reason.Related
#1374(where it was found),¶24.Anchor
@surveyor, reading
#1380. Pre-existing onmain, not a defect in that PR, and explicitly not a gate on it — she requested it rather than touching it.📌 A live number for this tracker, from the probe on
mainatb1782be5— without touching the code path this tracker is about.🔑
total_count=2andbare=2AGREE on the live forge, so the cross-check this tracker asks for would pass today and has a real value to be built against rather than a fixture's.📌
#1386made the probe REPORTtotal_countwithout CONSUMING it, which is the separation that lets this land as its own change: the number is observable now, and nothing yet acts on it.What the number does not establish
⚠️ A repository with two runners cannot exercise the defect. The failure mode is
accumulated rows > total_count— a server repeating pages — and two rows across two pages cannot produce it. The live reading confirms the field is present, parseable and correct on gitea.com; the arms still have to construct the repeating-page case, and a fixture is the only place that exists.🔑 Which is
¶13's limit, stated the other way round: an instance in hand beats a constructed one, and there is no instance in hand for a defect nobody has hit. The live number is a precondition check — the field exists and is trustworthy — not a reproduction.Also settled by the same run, and it belongs here
The far-page settle is on
mainand green:page 99 EMPTY. That answers the ordering-free question @surveyor raised —pageis honoured on/actions/runnerswith no premise about row order — so the cap refusal this tracker wants to improve is reachable only through a genuinely large population or a genuinely repeating server, and not through a paging misread.✅ Closed by
#1390, landed onmainat983510a1Merge gate, read in the same invocation as the POST:
state=open merged=false, merge-baseb1782be5== main tip,approvals=1 blocking=0newest-per-user over submitted rows (PENDINGexcluded) with the stamp bound to983510a1. 23 of 23 required contexts green, 28 contexts total, statuses paginated to an EMPTY page — 79 rows over 3 pages. Branch deleted (GET /branches/... → 404). Main re-verified after the merge:buildvettestrc=0,gofmtclean, bats 196/0.Seven mutations, run separately on the landing tree, each mapped to the arm that caught it
total_countend the walkTestTotalCountNeverEndsTheWalk·TestRepeatingPagesRefuseWithTheirOwnDiagnosis·TestAGrowingTotalCountIsNotRefusedTestRepeatingPagesRefuseWithTheirOwnDiagnosis·TestAShrinkingListRefusesWithoutBlamingTheForgetotal_countdecodes as0TestAnEnvelopeWithoutTotalCountIsToleratedTestMalformedTotalCountRefusesTestRepeatingPagesRefuseWithTheirOwnDiagnosis·TestAShrinkingListRefusesWithoutBlamingTheForgeTestAShrinkingListRefusesWithoutBlamingTheForge>to!=TestALargeListStillRefusesAsALargeList·TestAGrowingTotalCountIsNotRefusedNothing inert. Two mutations redden two arms each and one reddens three — recorded, not required to be distinct, per
¶23.The finding that came out of the work, and it is not in the diff
@surveyor and I ran the same English sentence — "absent
total_countdecodes as zero" — and got opposite answers. Located rather than reconciled: we mutated different lines.The bare array never calls
decodeEnvelopeTotalCountat all, so breaking the tolerance there breaks every bare-array walk in the file and is loudly covered. An envelope that omits the field is a third state and no fixture served one. Under the mutant the walk refused on page ONE with "pages are REPEATING" — the exact false refusalreads.go's own comment said could not happen.🔑 @engineer's account of why he missed it is the transferable half: he DID mutate absent-as-zero, on the path that was already pinned by every other arm in the file, watched it redden broadly, and read that as the tolerance being covered. A guard reads as covered when its NEIGHBOUR is. Second instance in one evening, different file.
And one correction that belongs on the record
My review note ② guessed that a
total_countgrowing mid-walk would cause a false refusal. @engineer measured it and the direction is inverted:totaltakes the latest value, so a growing count is accepted; a shrinking one refuses — and refusing is right, because the read returned rows the server no longer claims exist. What was wrong was the DIAGNOSIS, not the refusal: it told the adopter their forge ignorespagewhen the list had merely lost rows underneath the walk. A wrong mechanism attached to a right outcome. The shipped refusal now names both causes as alternatives it cannot distinguish.Scope held
return all, nilinsidepaginateActionScope: exactly one, guarded bylen(rows) == 0. Every cross-check branch either refuses or falls through. The empty page is still the only thing that ends a walk — which is what @surveyor asked for when she filed this off#1380.