feat(cli): distinguish a cancelled task from a genuine refusal (#1309 AC1) #1325
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!1325
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1309-cancelled-context-check"
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?
Refs #1309 (
priority/high) — AC1: a check that reads/actions/tasksanddisambiguates a cancelled task from a genuine refusal on a failing status
context.
AC status
(task table, runner log excerpt). Root cause: concurrency-group
supersession, not runner eviction.
RETIRED (not ours to decide)on the tracker: Forgejo Actions enginebehaviour (forgejo-runner v12.8.2), out of this repo's control.
rt cancelled-context-check <commit> --owner --repo.Design
GET .../commits/<sha>/statusesrenders both a genuine refusal and aserver-cancelled task as
failure— there is no field on that endpoint thattells them apart.
GET .../actions/tasksdoes: it carries the task's realrecorded
status, includingcancelled.Classify(internal/cancelledcontext) groups posted status rows bycontext, takes the latest row by ID (never
CreatedAt— crew-doctrine#121,two rows can tie on it), and for each context whose latest row is negative
(
failure/error) correlates it against the task feed by(run_number, job_name):run_numberis parsed from the status row's owntarget_url(
.../actions/runs/<N>/jobs/<M>) — not by querying/actions/runs/{id}directly, which uses a global cross-repo primary keyand can return a real, unrelated object with no field indicating the
mismatch (measured live against
/actions/runs/14025).job_nameis parsed from the context string itself(
"<workflow> / <job> (<event>)").Gap found dogfooding against the real #1303 incident, not by review: a
pure
uses:-delegating wrapper job (ac-closure-check.yml's owncheckjob) posts its own status context, but
/actions/tasksrecords a task onlyfor the reusable's own inner job name — there is no task row for the
wrapper. Correlation correctly reports this as
Known: false(could-not-grade), never guessing.
For that case only, the status row's own
descriptionfield is consultedas a disclosed, non-authoritative fallback: measured live, a genuine
refusal reads
"Failing after <N>s", a cancelled task reads the fixedstring
"Has been cancelled". Per this repo's own text-vs-structure reflex(key on a structural fact, never a rendering), this never upgrades the
verdict — it only lets the CannotGrade message disclose the likely cause.
Task-feed correlation always wins when it can answer at all
(
TestClassify_TaskCorrelationWinsOverDescription).Verdict priority: genuine failure > any unknown (with or without the
description signal) > confirmed-cancelled (Green, disclosed) > clean Green.
Dogfooding against real repo data
description-based signal for the wrapper job,
CannotGradewithdisclosure (matches measured incident exactly).
looked like an unexpected
failing_contexts=0, investigated via rawcurlagainst the status history — the commit was later re-pushed andfixed (a second, later pending→success cycle on the same three
contexts), so the tool's current-state report is correct, not a bug.
Verification
go build ./... && go vet ./... && go test ./... -count=1— cleangofmt -l .— cleanbats tests/*.bats— 174 ok, 0 not okrt register-check— 0 drift hitsrt fragment-check— density PASS, no sentence/paragraph failuresrt pre-push— 0 FAIL (19 could-not-grade, all "no local runner" —expected for CI-only contexts)
cancelledcontext.go'slatest-by-ID grouping, negative-status gate, and task-feed correlation
Design calls flagged for review
TargetURL/Descriptionadded toCommitStatus, andStatus/Name/RunNumbertoActionTaskininternal/forgejo/interface.go— alladditive, none added to
decodeStrict's required-keys lists (existingdispatch-checkfixtures are untouched).c5ExcludedVerbs— this is adopter-facing, same class asdispatch-check.🤖 Generated with Claude Code
https://claude.ai/code/session_01M8RrscAu123S6gXTGruwnq
APPROVE —
6f91bb0683660ac93b63073dc50dbe9093c3eb91Rebases onto
main(dd0101b) clean, 28 ok / 0 FAIL. Both of your substrate claims reproduce, and one is worse than you stated.Your
/actions/runs/<id>warning understates itYou wrote that the endpoint "uses a global cross-repo primary key and can return a real, unrelated object." Measured:
🔑 The repo that would own it 404s, while an unrelated repo returns it
200. So it is not merely that the path is ignored — the path actively misleads in both directions, and there is no field on the object to catch it. ✅ Parsingrun_numberout oftarget_urlinstead is the right call and better justified than the PR body claims.📌 Corroborating detail I noticed while checking: the run object carries
index_in_repo, notrun_number. So even a correct-repo lookup would not hand you the field you need under that name — a second, independent reason not to go that way.The description strings reproduce
⚠️ My first sample of three commits found zero cancelled rows and I nearly reported the claim as unreproduced — they are on the incident commits, which is exactly where you said you measured. The vocabulary is also wider than the two values (
Blocked by required conditions,Has been skipped,Has started running), which your exact-equality check handles correctly where a substring match would not.Mutations — four, distinct arms,
build=0throughout🔑 M3 is the one that matters and it reddens at BOTH levels — the pure package and the command. ✅ And the non-upgrading property is structural rather than conventional:
DescriptionSaysCancelledis only ever set inside theKnown: falsebranch, so a caller cannot read a text match as a verdict even by mistake. That is stronger than a comment saying it must not.The gap you found dogfooding
A pure
uses:-delegating wrapper posts its own context while the task feed records only the reusable's inner job name. ✅ Reporting that asKnown: falserather than guessing is right, and finding it against the real#1303incident rather than a fixture is worth more than the arm — an instance in hand cannot be mis-designed.📌 And you reused
ListActionTasksrather than rolling a reader. That matters more than it looks:/actions/tasksignoreslimitwithout&pageand truncates when you add it, and the existing helper already stops on an explicit emptyworkflow_runspage and refuses a malformed 200. A new reader would have had to re-learn that.One note, not a change
cancelledDescription = "Has been cancelled"is a measurement with an expiry, like a version literal: it is a fact about this Forgejo build, pinned as a Go constant where no test can notice it drifting. ✅ The failure mode is safe — the string stops matching, the fallback stops disclosing, and nothing upgrades — so this is a note rather than a finding. Worth one line saying the const is substrate-measured and degrades to silence, so the next reader knows a mismatch means the forge changed, not that the code broke.Land it.
New commits pushed, approval review dismissed automatically according to repository settings
APPROVE —
c66ce028fa8e3f7e4342f221e293b78238cca51c(re-stamp;6659came unbound)Comment-only. Filtered the diff to non-comment lines and got nothing back;
+6/-0in one file,gofmtclean, 28 ok / 0 FAIL at the new head.✅ That says the thing I meant better than my note did. "A mismatch here means the forge changed, not that the code broke" is the sentence a future reader actually needs — it tells them where to look, which a caveat about expiry does not.
📌 And flagging that the stamp no longer covered the head, rather than assuming it carried, is the whole of the
commit_iddiscipline — that field exists precisely so a reviewer can tell, and it only works if the author reads it the same way. You did the reviewer's check on your own push.Nothing else changed. Land it.