From 9f54eb98bf415a24b0bf4fe0510ff54c83d72e96 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Tue, 25 Aug 2026 08:41:27 +0000 Subject: [PATCH 1/4] test: expose missing Forgejo workflow names --- test/forge-backends.test.sh | 12 ++++++++-- test/labels-reconcile.test.sh | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/test/forge-backends.test.sh b/test/forge-backends.test.sh index 783530f..919347e 100644 --- a/test/forge-backends.test.sh +++ b/test/forge-backends.test.sh @@ -693,8 +693,9 @@ pr_view_stub() { } # The FAILURE is older but listed second — array order would pick it. FAKE_STATUS='{"state":"failure","statuses":[ - {"context":"ci / check","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"}, - {"context":"ci / check","status":"failure","created_at":"2026-08-02T09:00:00Z","updated_at":"2026-08-02T09:00:00Z"}]}' + {"context":"labels / labels (pull_request)","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"}, + {"context":"labels / labels (pull_request)","status":"failure","created_at":"2026-08-02T09:00:00Z","updated_at":"2026-08-02T09:00:00Z"}, + {"context":"codecov","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"}]}' FAKE_PR='{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"ancestor","draft":false,"mergeable":true}' pr_view_stub view_json="$(REPO=o/r forge_pr_view 5)" @@ -702,6 +703,13 @@ check "pr_view maps createdAt" 0 "" \ grep -q '"createdAt": "2026-08-02T10:00:00Z"' <<<"$view_json" check "pr_view maps completedAt" 0 "" \ grep -q '"completedAt":' <<<"$view_json" +check "pr_view derives workflowName from the first context separator" 0 "" \ + jq -e 'any(.statusCheckRollup[]; + .context == "labels / labels (pull_request)" and .workflowName == "labels")' \ + >/dev/null <<<"$view_json" +check "pr_view leaves workflowName empty when the context has no separator" 0 "" \ + jq -e 'any(.statusCheckRollup[]; + .context == "codecov" and .workflowName == "")' >/dev/null <<<"$view_json" check "pr_view maps mergeable to the UI string" 0 "" \ grep -q '"mergeable": "MERGEABLE"' <<<"$view_json" diff --git a/test/labels-reconcile.test.sh b/test/labels-reconcile.test.sh index c759891..396dcb3 100755 --- a/test/labels-reconcile.test.sh +++ b/test/labels-reconcile.test.sh @@ -589,6 +589,51 @@ expect "a rollup of ONLY self entries is honestly NONE, never SUCCESS" NONE \ expect "a genuine foreign FAILURE still blocks beside a cancelled self entry" FAILURE \ "$(rollup "[$(run_ a FAILURE),\ $(rec_ CANCELLED 2026-08-01T15:17:56Z 2026-08-01T15:17:59Z)]" | checks_state)" + +# Forgejo exposes Actions checks only as commit statuses. Drive the real +# backend mapper here: fixtures that hand-write workflowName repeat the field +# omission that hid #243 instead of proving the adapter supplies it. +forgejo_rollup() { + local statuses="$1" + ( + # shellcheck source=lib/forge-forgejo.sh + . lib/forge-forgejo.sh + forge_api() { + case "$1" in + repos/o/r/pulls/5) + printf '%s\n' '{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"ancestor","draft":false,"mergeable":true}' + ;; + repos/o/r/commits/abc/status) + printf '%s\n' "$statuses" + ;; + *) return 1 ;; + esac + } + REPO=o/r forge_pr_view 5 + ) +} + +forgejo_self_red='{"state":"failure","statuses":[ + {"context":"labels / labels (pull_request)","status":"failure","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}, + {"context":"CI / test (pull_request)","status":"pending","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}]}' +expect "Forgejo StatusContexts exclude a self FAILURE and retain pending work" PENDING \ + "$(forgejo_rollup "$forgejo_self_red" | checks_state)" + +forgejo_self_only='{"state":"failure","statuses":[ + {"context":"labels / labels (pull_request)","status":"failure","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}]}' +expect "Forgejo StatusContexts containing only the self workflow are NONE" NONE \ + "$(forgejo_rollup "$forgejo_self_only" | checks_state)" + +forgejo_foreign_red='{"state":"failure","statuses":[ + {"context":"labels / labels (pull_request)","status":"failure","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}, + {"context":"CI / test (pull_request)","status":"failure","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}]}' +expect "Forgejo StatusContexts retain a genuine foreign FAILURE" FAILURE \ + "$(forgejo_rollup "$forgejo_foreign_red" | checks_state)" + +forgejo_bare_red='{"state":"failure","statuses":[ + {"context":"codecov","status":"failure","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}]}' +expect "a bare Forgejo context is never guessed to be the self workflow" FAILURE \ + "$(forgejo_rollup "$forgejo_bare_red" | checks_state)" # ...and an empty self filters NOTHING: outside Actions no workflow name is # ambient, and the exclusion must never drop entries on a guess — the same # displaced-self rollup keeps blocking there, all-cancelled context intact. From 21871de5ee88d036276306180f2d5cc65f37239c Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Tue, 25 Aug 2026 08:42:53 +0000 Subject: [PATCH 2/4] fix: restore Forgejo workflow names --- lib/forge-forgejo.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/forge-forgejo.sh b/lib/forge-forgejo.sh index 20deffe..415f0d6 100644 --- a/lib/forge-forgejo.sh +++ b/lib/forge-forgejo.sh @@ -468,6 +468,10 @@ forge_pr_view() { $st.statuses[]? | { __typename: "StatusContext", context: .context, + # Forgejo carries the workflow name only as the context prefix; + # no separator means no proven workflow, so never guess (#243). + workflowName: ((.context // "") + | if contains(" / ") then split(" / ")[0] else "" end), state: (.status | ascii_upcase), # checks_state groups repeated contexts and takes the NEWEST by # [.startedAt, .createdAt, .completedAt]. Without a timestamp the From 4bdb80bf8094c95b2ceea9c61987c7ecda6626a0 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Tue, 25 Aug 2026 08:43:15 +0000 Subject: [PATCH 3/4] docs: record Forgejo workflow-name fix --- changelog.d/243.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/243.md diff --git a/changelog.d/243.md b/changelog.d/243.md new file mode 100644 index 0000000..82dd6a5 --- /dev/null +++ b/changelog.d/243.md @@ -0,0 +1,3 @@ +### Fixed + +- Preserve Forgejo workflow names in status rollups so the label reconciler excludes only its own checks. (#243). From 54a933436361648e37707459ee88d9532887705e Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Tue, 25 Aug 2026 08:53:52 +0000 Subject: [PATCH 4/4] test: complete Forgejo workflow-name matrix --- test/forge-backends.test.sh | 15 ++++++++++++--- test/labels-reconcile.test.sh | 12 +++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/test/forge-backends.test.sh b/test/forge-backends.test.sh index 919347e..2f36510 100644 --- a/test/forge-backends.test.sh +++ b/test/forge-backends.test.sh @@ -695,6 +695,8 @@ pr_view_stub() { FAKE_STATUS='{"state":"failure","statuses":[ {"context":"labels / labels (pull_request)","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"}, {"context":"labels / labels (pull_request)","status":"failure","created_at":"2026-08-02T09:00:00Z","updated_at":"2026-08-02T09:00:00Z"}, + {"context":"CI / test (pull_request)","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"}, + {"context":"Refs guard / refs-not-closing (pull_request)","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"}, {"context":"codecov","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"}]}' FAKE_PR='{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"ancestor","draft":false,"mergeable":true}' pr_view_stub @@ -704,9 +706,16 @@ check "pr_view maps createdAt" 0 "" \ check "pr_view maps completedAt" 0 "" \ grep -q '"completedAt":' <<<"$view_json" check "pr_view derives workflowName from the first context separator" 0 "" \ - jq -e 'any(.statusCheckRollup[]; - .context == "labels / labels (pull_request)" and .workflowName == "labels")' \ - >/dev/null <<<"$view_json" + jq -e '[.statusCheckRollup[] + | select(.context == "labels / labels (pull_request)" + or .context == "CI / test (pull_request)" + or .context == "Refs guard / refs-not-closing (pull_request)") + | [.workflowName, .context]] + | unique == [ + ["CI", "CI / test (pull_request)"], + ["Refs guard", "Refs guard / refs-not-closing (pull_request)"], + ["labels", "labels / labels (pull_request)"] + ]' >/dev/null <<<"$view_json" check "pr_view leaves workflowName empty when the context has no separator" 0 "" \ jq -e 'any(.statusCheckRollup[]; .context == "codecov" and .workflowName == "")' >/dev/null <<<"$view_json" diff --git a/test/labels-reconcile.test.sh b/test/labels-reconcile.test.sh index 396dcb3..9dd791c 100755 --- a/test/labels-reconcile.test.sh +++ b/test/labels-reconcile.test.sh @@ -632,7 +632,17 @@ expect "Forgejo StatusContexts retain a genuine foreign FAILURE" FAILURE \ forgejo_bare_red='{"state":"failure","statuses":[ {"context":"codecov","status":"failure","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}]}' -expect "a bare Forgejo context is never guessed to be the self workflow" FAILURE \ +expect "a bare Forgejo context survives a named self-workflow exclusion" FAILURE \ + "$(forgejo_rollup "$forgejo_bare_red" | checks_state)" + +forgejo_nested_name='{"state":"failure","statuses":[ + {"context":"labels / nested / check","status":"failure","created_at":"2026-08-23T18:32:00Z","updated_at":"2026-08-23T18:32:00Z"}]}' +SELF_WORKFLOW="labels / nested" +expect "a Forgejo workflow name containing a separator is never guessed" FAILURE \ + "$(forgejo_rollup "$forgejo_nested_name" | checks_state)" + +SELF_WORKFLOW="" +expect "a bare Forgejo context also survives an empty self-workflow exclusion" FAILURE \ "$(forgejo_rollup "$forgejo_bare_red" | checks_state)" # ...and an empty self filters NOTHING: outside Actions no workflow name is # ambient, and the exclusion must never drop entries on a guess — the same