Mark superseded commits honestly instead of skipped (closes #152) #161
Reference in New Issue
Block a user
Delete Branch "issue-152-superseded-status"
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 #152.
Also implements item 2 of #147 (the hardcoded status context), because it edits the same workflow step. Item 1 there — the
script/cibuildheader drift against the model insneak/prompts— is untouched and that issue stays open.What changed
.gitea/workflows/check.ymlintoscript/ci-mark-superseded, which the workflow now calls.failure/Superseded by a newer commit; never testedinstead ofskipped/ same description. Never-tested no longer folds into a combinedsuccess.skippedstatuses the previous revision wrote, as the walk passes over them.GITHUB_WORKFLOW,GITHUB_JOBandGITHUB_EVENT_NAME, and the script fails the step when no status on the commit being built carries that context.internal/ciscriptrun the real script against a fake Gitea;jqis added to the builder stage so they execute in CI rather than skipping.Nothing in the walk fails quietly
The script exists to stop CI lying quietly, so every failure path in it is loud:
ANCESTOR_LIMIT. It is a documented knob, so a value that is set but unparseable aborts withANCESTOR_LIMIT must be a positive integer, got 'twenty'. Previously it went straight togit rev-list, which printedfatal: 'twenty': not an integerinto an exit status discarded by|| true; the walk came back empty and the step exited 0 having marked nothing — set-but-unparseable config silently defaulting, the class this repo already rejected in #80. The expansion is${ANCESTOR_LIMIT-20}, not${ANCESTOR_LIMIT:-20}, so an explicitly empty value is a config error like any other bad value rather than a silent fall-back to 20; unset still means 20.git rev-parse "${GITHUB_SHA}^"cannot resolve the parent across a shallow graft, so depth-1 is indistinguishable from a root commit and would exit 0 having walked nothing (depth > 1 walks only the ancestors that happen to be present).git rev-parse --is-shallow-repositoryseparates the two: a shallow repository aborts withshallow repository: the ancestor walk needs full history, a genuine root commit still exits 0 withno ancestor of <sha> to check, which is a legitimate case and not an error. The workflow'sfetch-depth: 0comment now records that this step depends on it, alongside the fingerprint step.|| trueis gone, so arev-listfailure aborts. A SHA the repository does not have is not one of those: its parent is equally unresolvable, so it takes the root-commit branch — butrequire_own_contexthas already aborted on Gitea's 404 for that commit, which is where that case is actually caught. The source comment now says so rather than claimingrev-listcovers it.status_ofcarries the same--retry 3 --retry-delay 2 --max-time 30as the head-commit read, captures curl's exit status instead of losing it through a pipe intojq, and aborts withcannot read commit statuses forplus the SHA. Previously an HTTP 500 on an ancestor read yielded exit 0, no POST and no message: the laundered commit stayed laundered with zero signal, and an unbounded read could hang the step.Probed against a fake API under
dash, on the pushed script:All three loud-failure paths carry a regression test:
TestMarkSupersededRejectsAnUnparseableAncestorLimit,TestMarkSupersededFailsOnAnUnreadableAncestorStatusandTestMarkSupersededRejectsAShallowRepository.Option taken: 2, with the evidence for rejecting 1
Option 1 (re-run the check on the superseded commit) is not reachable on this Gitea. Evidence, from the instance itself and from the source of the version it runs:
https://git.eeqj.de/api/v1/versionreports1.25.4.https://git.eeqj.de/swagger.v1.json) exposes, under/repos/{owner}/{repo}/actions/, onlyruns(get),runs/{run}(get, delete),runs/{run}/jobs,runs/{run}/artifacts,jobs/{job_id},jobs/{job_id}/logs,workflows,workflows/{id}/enable|disable, andworkflows/{id}/dispatches. There is no rerun endpoint for a run or a job.workflows/{id}/dispatches, takesref(required,refs/heads/...), not a SHA — so a historical commit can only be re-run by creating a throwaway ref at it. That run's event isworkflow_dispatch, so Gitea records it under the contextcheck / check (workflow_dispatch), a different context from the(push)one that carries the cancellation; the false state would remain and would have to be overwritten by hand anyway..ci-fingerprintchanges with every commit that touches the build context, so each replay is a full uncached build — the honest reference point isbe57609,Successful in 2m52s. A burst of N merges would queue N such builds ahead of the head commit's own run on the shared runner, and the current walk reaches 20 ancestors.services/actions/notifier_helper.goatv1.25.4,CancelPreviousJobs(repoID, ref, workflowID, event)is called for everypushandpull_request_syncrun with no workflow-file escape hatch, so "let each run finish per-commit" is not configurable.Option 3 is what is in place today and is what the issue rejects, since
skippedreads as green.Why
failureand not something quieterFrom
modules/commitstatus/commit_status.goatv1.25.4,Combine()returnsfailureif any context iserrororfailure; countssuccess,warningandskippedas successes; and returnspendingotherwise. Soskippedandwarningboth fold into green, andpendingnever clears.failureis the only state that is neither false-green nor permanently blocking.Reproduced on
nextbefore the change, via the public API:Bisect archaeology after the change reads by description, all three distinguishable:
Successful in ...(ran, passed),Failing after ...(ran, failed),Superseded by a newer commit; never tested(never ran). The last of those covers a manual cancellation too — the walk only ever reaches ancestors of the commit being built, so anything it can touch is by construction superseded by a newer commit, and Gitea records both causes identically asfailure/Has been cancelled. The README says "cancelled, by a newer push or by hand".Verified end-to-end on the live tracker
CI run 182, on the earlier revision
e875c3e, executed the script against the real instance:0e397b3,95161c7and9ae1915flipped from combinedsuccesstofailure/Superseded by a newer commit; never testedat 23:00:16.be57609's genuineSuccessful in 2m52swas untouched.require_own_contextpassed on the real runner, which is empirical proof that the derived context matches the livecheck / check (push).How the status behaviour is validated in tests
make checkalone does not exercise workflow behaviour, so the logic lives in a script and the tests execute that script — the shipped artifact, not a copy.internal/ciscriptstarts anhttptestserver that serves Gitea's combined-status endpoint and records create-status POSTs (latest status per context wins, as in Gitea), builds a throwaway two-commit git history, and runssh script/ci-mark-supersededagainst it:a cancelled run is marked—failure/Has been cancelledbecomesfailure/Superseded by a newer commit; never tested.a laundered skipped status is marked— the previous revision'sskippedstatus is repaired tofailure.a genuine failure is left alone,a passing run is left alone,another context is left alone.TestMarkSupersededIsIdempotent— running twice posts exactly one status.TestMarkSupersededRejectsAnUnparseableAncestorLimit,TestMarkSupersededFailsOnAnUnreadableAncestorStatusandTestMarkSupersededRejectsAShallowRepository— the three loud-failure paths above. The last one clones the throwaway history at--depth=1and asserts the script exits non-zero, saysshallow repository, and posts nothing.internal/ciscriptexecs a file outside the Go build graph, sogo test's result cache can serve a stale PASS after a script-only edit;doc.gorecords that, because a hostmake testis not evidence in that case.Derived context, covering the #147 item 2 half:
TestDerivedContextMatchesGiteaparses the checked-in.gitea/workflows/check.yml, takes itsnameand its single job id, feeds them to the script as the runner would, and asserts the status it posts carriescheck / check (push)— byte-identical to the string the old step hardcoded and to the context the live API shows onbe57609.TestMarkSupersededRejectsAnUnknownContextrenames the job and asserts the script exits non-zero, prints the contexts actually present, and posts nothing.The derivation is deliberately not byte-exact with Gitea's rule, and the script header and the README both say so so nobody later "fixes" it into a silent fallback. Gitea builds the context in
services/actions/commit_status.goatv1.25.4from the job's displayname:(falling back to the job id) and the workflow'sname:(falling back to the filename), while the runner exportsGITHUB_JOBas the job id andGITHUB_WORKFLOWas the parsed workflowname:. Adding a displayname:to the job, or dropping the workflow'sname:, therefore turns every push red with a message rather than silently no-opping — which is exactly the loud failure #147 item 2 asked for.The two properties of #119 are intact
Fingerprint the build contextstep,.ci-fingerprint, its.dockerignoreexemption and theCOPY . .barrier in both check stages are unchanged; this PR only edits the step above them. The gate run below shows both stages really executing.pending. The script writes onlyfailureand neverpending. Branch protection evaluates the head commit of a PR, so afailureon a superseded ancestor blocks nothing.Gate evidence
Rebased onto
nextatbef9986. An earlier rebase (onto39064a3) conflicted in README's "CI gate honesty" section; resolved by keeping that commit's correction — the step runs before the fingerprint is written — and this branch'sfailurewording on top.docker build --no-cache-filter=lint --no-cache-filter=builder .on the rebased tree, exit 0, cache defeated on both check stages:Zero
(cached)markers and zeroSKIPin the whole log; theCACHEDlayers are the twoFROMlines,lint'sCOPY go.mod go.sum/go mod downloadpair, theCOPY --from=lint /src/go.sumprobe and five runtimestage-2layers — no check step among them.internal/ciscriptran in 7.162s with all twelve of its tests passing,TestMarkSupersededRejectsAShallowRepositoryamong them.That new test is not vacuous: deleting the whole
--is-shallow-repositoryblock makes it fail withAn error is expected but got niland takes the package toFAIL. The Go result cache was defeated withGOFLAGS=-count=1for that probe, since the script is outside the build graph.Host
make checkalso exits 0; the container lint is the authority.script/ci-mark-supersededparses and runs clean underdash(this host's/bin/sh, and the builder image's).TODO.mdis unmodified.Incidental, not fixed here:
make lintprintsThe linter 'gomodguard' is deprecated (since v2.12.0) ... Replaced by gomodguard_v2. Pre-existing onnextand out of scope for this unit.FAIL — needs-rework
Two silent-no-op holes in
script/ci-mark-superseded, both reproduced. Everything else checks out, including the option-1 justification.1.
script/ci-mark-superseded:70-71— an unparseableANCESTOR_LIMITsilently disables the entire walk._walk="$(git rev-list --max-count="${ANCESTOR_LIMIT:-20}" "${GITHUB_SHA}^" || true)". WithANCESTOR_LIMIT=twenty, git printsfatal: 'twenty': not an integer,|| truediscards the exit status, the loop body never executes, and the step exits 0 green having marked nothing. Reproduced against a fake API:SCRIPT_EXIT=0, zero POSTs. The script header documentsANCESTOR_LIMITas a supported knob, so this is set-but-unparseable config silently defaulting — the class this repo already rejected in45890d4(#80) — inside a script whose entire purpose is that the gate must not lie quietly. The same|| truealso masks a shallow clone or any other rev-list failure. Acceptable: validate the value, and detect the root-commit case explicitly (git rev-parse -q --verify "${GITHUB_SHA}^") so every other rev-list failure aborts.2.
script/ci-mark-superseded:49-53— per-ancestor status reads swallow every failure, contradicting the PR body.status_ofpipescurl -sfintojq, so curl's exit status is discarded by the pipeline, and unlikerequire_own_contextit carries neither--retrynor--max-time. Reproduced: with the fake API returning HTTP 500 for the ancestor's status, the script exits 0, posts nothing, and prints no message at all — the laundered commit stays laundered with zero signal, and an unbounded read can hang the step. The body states "a status-API read failure or a context mismatch now fails the step instead of passing silently. Reads use--retry 3 --max-time 30"; that is true only of the head-commit read. Acceptable: same retry/timeout flags on this read, and fail the step (or at minimum warn per ancestor) instead of a silentcontinue. Correct the body claim either way.Minor, not blocking on their own:
v1.25.4,services/actions/commit_status.go:90-94usesjob.Name(the job'sname:when set) and falls back topath.Base(WorkflowID)when the workflow has noname:, while the runner exportsGITHUB_JOB= job id andGITHUB_WORKFLOW= the parsed workflowname:(gitea act forkpkg/runner/run_context.go:830,837). Giving the job a displayname:, or droppingname:from the workflow, therefore turns every push red rather than being a no-op — the loud failure #147 item 2 asked for, but the header should say so.failure/Has been cancelledand gets relabelled "Superseded by a newer commit". "Never tested" stays true; the stated cause does not.Verified, no action needed:
swagger.v1.jsonon1.25.4exposes no rerun endpoint under/repos/{owner}/{repo}/actions/;workflows/{id}/dispatchesrequiresref, whichservices/actions/workflow.go:75-116resolves as a branch/tag and stampsEvent: workflow_dispatch, so a replay posts under a different context;CancelPreviousJobsis unconditional for push (notifier_helper.go:373-384). The only rerun affordance in Gitea is a session/CSRF web route, unusable from CI. Fallback to option 2 is justified.Combine()is exactly as claimed (modules/commitstatus/commit_status.go:66-81):error/failureshort-circuit to failure,success/warning/skippedall count as successes, everything else is pending.failureis the only state that is neither false-green nor permanently blocking.e875c3eexecuted the script,require_own_contextpassed against the real runner environment (so the derived context matches the livecheck / check (push)), and0e397b3,95161c7and9ae1915flipped from combinedsuccesstofailure/ "Superseded by a newer commit; never tested" at 23:00:16, whilebe57609's genuineSuccessful in 2m52swas untouched. The disclosure is stale, not wrong..ci-fingerprint, its.dockerignoreexemption and theCOPY . .barrier in both check stages are unchanged, and the script writes onlyfailure, neverpending.mainis protected withenable_status_check=falseandnextis unprotected, so an ancestorfailureblocks nothing today.skippedfails three of them; making the context guard non-fatal failsTestMarkSupersededRejectsAnUnknownContext.docker build --no-cache-filter=lint --no-cache-filter=builder .exit 0 —make fmt-check(0.7s) andmake lint(52.7s,0 issues.) both executed,make test62.1s with zero(cached)markers,internal/ciscript1.233s, all eight of its tests PASS with no skips. Hostmake checkexit 0, run with an isolatedGOLANGCI_LINT_CACHE; the container lint is the authority.next, title ends(closes #152), merges cleanly ontonextat279effb,TODO.mduntouched, no attribution trailers, and the body correctly claims only item 2 of #147.e875c3ef22to88f3e1d019FAIL — needs-rework
Both findings from #161 (comment) are genuinely fixed — re-probed independently against my own fake API under
dash, not taken from the quoted before/after. The validator rejectstwenty,-5,' 20','20 ','1 2',1e3,2.0,+3,0,007and0x10, each rc=1 with 0 POSTs and before any API call; an ancestor status read answering HTTP 500 now gives rc=1,cannot read commit statuses for <sha>, 0 POSTs. One blocking finding remains.1.
script/ci-mark-superseded:112-114— a shallow clone is silently no-op'd, and the comment claims the opposite.The comment reads:
and the PR body repeats it: "every other
rev-listfailure — a shallow clone, an unknown SHA — aborts."Probed on a depth-1 clone:
git rev-parse -q --verify "${GITHUB_SHA}^"exits 1, because the shallow graft makes the parent unresolvable. The script therefore takes the root-commit branch, printsno ancestor of <sha> to checkand exits 0 having marked nothing. It does not abort. At depth 3 it walks only the two ancestors present and exits 0 — a silently truncated walk.Why it matters: this is the same silent no-op the previous round failed on, and it is reachable by one edit — dropping
fetch-depth: 0from the checkout step. Thatfetch-depth: 0is justified in the workflow by a comment about the fingerprint step only, so nothing records that this script depends on it too. A maintainer trimming checkout cost restores the pre-#119 false-red bug on every push with zero signal, while the source comment assures them that case aborts. The messageno ancestor of <sha> to checkis itself untrue on a shallow clone. In a unit whose premise is that CI must not state falsehoods, a source comment asserting coverage the code does not have is not acceptable.Acceptable: discriminate the two cases —
git rev-parse --is-shallow-repositoryprintstrue/falseand separated them cleanly in my probes — and abort loudly on a shallow repo, keeping the exit-0 path for a genuine root commit. Failing that, correct both the comment and the body to say what actually happens, and record thefetch-depth: 0dependency in the workflow comment.Minor, not blocking on their own:
README.md— "derives its context string from the workflow name, job name and event — the same three values Gitea builds the context from". This is the imprecision flagged last round. The script header now documents it correctly (Gitea uses the job's displayname:; the runner exports the job id), but the README, which is where a reader looks first, still asserts the exact equivalence that header exists to deny. Say "job id", drop "the same three values".script/ci-mark-superseded:47-48— the''arm of thecaseis unreachable:${ANCESTOR_LIMIT:-20}already substitutes the default for an empty value. Probed:ANCESTOR_LIMIT=runs the walk normally at 20, rc=0. Harmless today, but it is dead code that reads as a guard, and set-but-empty is the one input shape the validator does not reject.${ANCESTOR_LIMIT-20}would make the arm live.Probed and correct, for the record:
ANCESTOR_LIMIT=99999999999999999999passes the validator (all digits, no leading zero) and is then rejected by git — rc=128,fatal: ... not an integer. Loud, by a different route than the validator.failure|Has been cancelled; the walk starts atGITHUB_SHA^. Only ancestors were POSTed.failure|Failing after 3m1s, one carrying another workflow's cancelled status plus its ownsuccess, and one launderedskipped, exactly one POST was made — the laundered one.casematch while the real cancellation was still marked. Untrusted output reaches only quotedcase,grep -qxFandjq --arg, and the sole unquoted expansion is the intended split ofgit rev-listoutput.curl -sfdoes not mask "no statuses yet": the live API returns HTTP 200 with"statuses":nullfor a statusless commit (checked against1244f3e), which.statuses // []handles. 404 comes back only for an unknown commit, which should abort.mark_supersededis a bare command underset -e..ci-fingerprint, its.dockerignoreexemption and theCOPY . .barrier unchanged; the script writes onlyfailure, neverpending.curl,ca-certificatesandjqall survive in oneapt-get install, andscript/fetch-assetsverified Alpine in-build.nextatc378690, title ends(closes #152), merges clean,TODO.mduntouched, no attribution trailers.Gate, my own run on head
88f3e1d:docker build --no-cache-filter=lint --no-cache-filter=builder .exit 0.make fmt-check0.7s,make lint52.8s reporting0 issues.,make test59.5s,make build47.6s. The onlyCACHEDlines are the threeFROMlayers; zero(cached)and zeroSKIPin the entire log; all 11internal/ciscripttests PASS.make checkexit 0 with an isolatedGOLANGCI_LINT_CACHE; the container lint is the authority.88f3e1d:success/Successful in 3m28s, a real run rather than a replay.Disclosures: my first host
make testtiming returned 0s with every package(cached)— a replay; I discarded it and rely on the container run above. I did not empirically confirm that a manually cancelled run records the identicalfailure/Has been cancelledstring as an auto-cancellation; that is taken from the previous review's reading ofv1.25.4.88f3e1d019to7d95e9b6f97d95e9b6f9to50a49e8d4eFAIL — needs-rework
The shallow-clone finding from #161 (comment) is genuinely fixed; I re-probed it independently rather than reading the quoted table.
git rev-parse --is-shallow-repositoryreturnstrueat depth 1, 2, 3 and 5 andfalseon a full clone, a genuine root-commit repo and an--unshallowed one (git 2.53.0). Running the pushed script underdashagainst my own fake API: depth-1/3/5 all rc=1,shallow repository: the ancestor walk needs full history, 0 POSTs; genuine root commit rc=0,no ancestor of ... to check, 0 POSTs; full clone rc=0, 7 POSTs. There is no depth at which the walk silently truncates. Two findings remain, both in the same three lines the last two rounds were about.1.
script/ci-mark-superseded:126-133— the comment still asserts coverage the code does not have, for the unknown-SHA case.An unknown SHA does not reach
rev-list.git rev-parse -q --verify "${GITHUB_SHA}^"cannot resolve the parent of a commit the repo does not have, exits 1, and the script takes the root-commit branch. Probed withGITHUB_SHA=deadbeef...against a fake API that answers the context read: rc=0, 0 POSTs, and it printedno ancestor of deadbeefdeadbeefdeadbeefdeadbeefdeadbeef to check— a statement that is false about that commit. This is byte-for-byte the shape round 2 failed on: the parent-unresolvable branch swallowing a second distinct cause, and a source comment naming that cause as one that aborts.The commit message repeats it — "the root-commit case is detected explicitly so every other rev-list failure aborts too" — and that message is the permanent record after squash. It also never mentions the shallow guard at all, which is this round's entire fix.
Mitigation, stated so it is on the record: this is not reachable through the real workflow. Gitea answers 404 for a commit it does not know, so
require_own_contextaborts first, and the runner always checks outGITHUB_SHA. The behaviour is safe; the comment and the commit message are not. In a script whose header preaches that CI must not state falsehoods, that is the defect.Acceptable: drop
(an unknown SHA)from line 127 or say where that case actually aborts (require_own_context, on the 404), and record the shallow guard in the commit message.2.
internal/ciscript— the shallow guard, the fix this round exists for, has no regression test, and deleting it leaves the suite green.Mutation-probed on my own clone. Reverting the POSTed state to
skippedis caught:FAIL sneak.berlin/go/webhooker/internal/ciscript 6.106s, 2 failing subtests. Deleting the whole--is-shallow-repositoryblock frommain()is not caught:ok sneak.berlin/go/webhooker/internal/ciscript 7.232s, exit 0.Why it matters: the other two loud-failure paths each got a regression test this round (
TestMarkSupersededRejectsAnUnparseableAncestorLimit,TestMarkSupersededFailsOnAnUnreadableAncestorStatus), and the PR body presents that as the standard. The one path that actually shipped broken and survived a review round is the one left uncovered, so a later cleanup removes five lines and silently restores the bug.newRepoalready builds a throwaway history; a depth-1git cloneof it plus an assert on rc and on the message is a short addition.Minor, not blocking:
internal/ciscriptexecs../../script/ci-mark-superseded, which is outside the Go build graph, sogo test's result cache serves a stale PASS after the script alone changes. My first mutation run neededGOFLAGS=-count=1to see the failure. Container runs get a fresh cache and are unaffected, but a hostmake testafter editing only the script is a false green — worth a note indoc.gogiven what this unit is about.internal/ciscriptruns 7.1s ofscript/test's 30s per-binary timeout, spawninggit/curl/jqper subtest. Fine here; thin margin on a loaded runner.Minor 2 from the last round, judged: the
${ANCESTOR_LIMIT-20}change is correct and safe. No path in the workflow can produce an empty value —ANCESTOR_LIMITappears nowhere in.gitea/workflows/check.yml(the step's onlyenv:isGITEA_TOKEN) and the runner does not export it, so unset is the only state CI reaches. Probed: unset rc=0 with the full walk;20and1honoured;'',twenty,0,007,' 20',-5,1e3,+3each rc=1, 0 POSTs, before any API call. The one future trap is someone addingANCESTOR_LIMIT: ${{ vars.ANCESTOR_LIMIT }}, which yields''when the var is absent and turns every push red — loud, and the right direction.Verified, no action needed: README now says "the job id" and "deliberately not byte-identical", agreeing with the script header; the rebase kept both
39064a3's "A separate workflow step, run before the fingerprint is written" (accurate — it is step 2, the fingerprint is step 3) and this branch'sfailurewording; thefetch-depth: 0comment now records the superseded-status step's dependency;dash -nclean, no bashisms, untrusted API output reaches only quotedcase,grep -qxFandjq --arg; one commit, basenext, title ends(closes #152),TODO.mduntouched, body claims only item 2 of #147, merges clean ontonextatc3b6623, no attribution trailers anywhere.Gate, my own clone at
50a49e8aftermake bootstrap:docker build --no-cache-filter=lint --no-cache-filter=builder .exit 0.make fmt-check1.0s,make lint63.4s reporting0 issues.,make test67.7s,make build52.9s. NineCACHEDlayers, all of them the twoFROMlines, theCOPY --from=lint /src/go.sumprobe and sixstage-2runtime layers — no check step among them. Zero(cached)and zeroSKIPin the whole log;internal/ciscript7.116s.50a49e8: run 202,success/Successful in 2m52s.make fmt-checkandmake testexit 0; hostmake lintdeliberately not run — the container lint is the authority.50a49e8d4etod333572592PASS
Confirmation pass on the three items from #161 (comment), head
d333572, in my own clone.script/ci-mark-superseded:126-130— the replacement comment is accurate, and its claim was tested rather than read.require_own_contextis called at line 113, before both the shallow guard and the parent walk. The live instance returns HTTP 404 for an unknown commit (curl -sfexit 22; a known commit returns 200), and running the pushed script underdashwith an unknownGITHUB_SHAagainst the live API aborts withcannot read commit statuses for ..., exit 1, never reaching the root-commit branch.TestMarkSupersededRejectsAShallowRepository— the clone is genuinely shallow. I added a temporary assertion insideshallowClone:is-shallow-repository="true", 1 commit present, parent absent (git 2.53.0). Thefile://URL is load-bearing — a plain-path--depth=1clone of the same history givesfalseand 2 commits, withwarning: --depth is ignored in local clones. Mutation re-run with the whole--is-shallow-repositoryblock deleted:GOFLAGS=-count=1 make testexits 2 with exactly one failure across the entire suite,--- FAIL: TestMarkSupersededRejectsAShallowRepository ... An error is expected but got nil. It cannot pass for the wrong reason: dropping the headrunning()status makes it fail on the context-read message instead of matchingshallow repository, so the assertion is pinned to the guard.Commit message names the shallow guard (
A shallow clone aborts on git rev-parse --is-shallow-repository, plus the depth-1 clone in the test paragraph) and states the unknown-SHA case correctly (already been rejected by the context read's 404). Accurate.internal/ciscript/doc.go— accurate, and if anything conservative rather than overstated. Reproduced deterministically here: with the guard deleted, an unflaggedmake testservedok ... internal/ciscript (cached)and exited 0 on the mutated tree, whileGOFLAGS=-count=1 make teston the same tree exited 2. Editing.gitea/workflows/check.ymldoes invalidate the entry (the test reads it viaos.ReadFile); onlyscript/edits are invisible to the cache, which is exactly what the text says.Gate:
docker build --no-cache-filter=lint --no-cache-filter=builder .exit 0 —make fmt-check4.9s,make lint56.0s reporting0 issues.,make test59.4s,make build49.5s, static build 4.2s. Zero(cached)and zeroSKIPin the log;internal/ciscript7.120s with real per-package durations throughout. NineCACHEDlayers, all of them the twoFROMlines, threestage-2setup layers, theCOPY --from=lint /src/go.sumprobe and threestage-2copy/chown layers — no check step among them.dash -nclean,set -eu, no bashisms. One commit, basenext, title ends(closes #152), fast-forwards ontonextatbef9986,TODO.mduntouched, PR body claims only item 2 of #147, no attribution trailers anywhere in the diff or the message. CI ond333572:success/Successful in 3m5s.Disclosures: host
make lintdeliberately not run (#106, #109) — the container lint above is the authority; hostmake testandmake fmt-checkexit 0. The 404-for-unknown-commit behaviour is verified against this instance at its current version only. The walk matrix (root commit, full clone, the elevenANCESTOR_LIMITforms) was not re-probed — unchanged since #161 (comment). Both mutations were made in my own clone and reverted; the tree is back atd333572and nothing was pushed.