The workflow shipped in #103 and failed on every close, on its second line, before reaching the API: shell: sh -e {0} /var/run/act/workflow/0.sh: 2: set: Illegal option -o pipefail Inside `container:` the act runner selects sh, not bash, and `set -o pipefail` is a bashism. homebrew-formula.yml carries the same line without trouble because it runs with no container, on the host image where bash is the default -- so "another workflow does it" was not the evidence it looked like, and the comment now says so where the next person will read it. pipefail is kept rather than dropped for POSIX's sake: the lookup is `curl -sSf ... | jq`, so without it an API error yields empty output, an empty label id, and a cheerful "nothing to do" on every close. A silent no-op is the one outcome worse than a failing job here. Validated end to end against scratch issues rather than by reading it: with the label present the step returns 204 and the label is gone, and against an issue that never carried it the step also returns 204 and exits 0 -- which is what makes it safe to run on every close rather than only claimed ones. Still untested: whether secrets.GITEA_TOKEN carries issue-write scope. The old run never got far enough to find out. If it 403s, the fix is one line -- secrets.PACKAGE_TOKEN, which is a user PAT. Closes #102
94 lines
3.7 KiB
YAML
94 lines
3.7 KiB
YAML
name: Unclaim
|
|
|
|
# A `Closes #N` footer in a commit body closes the issue on merge — and
|
|
# leaves `Status/In Progress` on it, because Gitea's auto-close touches
|
|
# state and nothing else. So #100 was closed and simultaneously marked
|
|
# as being actively worked on, and `scripts/issue.sh close` (which does
|
|
# drop the label) is exactly the thing the footer exists to avoid
|
|
# calling.
|
|
#
|
|
# **This hooks the close, not the merge.** Stripping the label in the
|
|
# PR would work and would be a per-PR habit; habits are what the footer
|
|
# removed. `issues: [closed]` covers every path an issue can close by —
|
|
# the footer on merge, `issue.sh close`, someone clicking Close in the
|
|
# web UI — and asks nothing of anyone at any of them.
|
|
#
|
|
# **Reopening deliberately does not restore it.** Reopening says the
|
|
# work was not finished, not that somebody is at a keyboard doing it
|
|
# now; the claim gets re-made by whoever picks it up.
|
|
#
|
|
# **This is not instant, and should not be described as it.** The
|
|
# runner has capacity 1 and is shared with an index build that can hold
|
|
# it for three hours, so a label tweak can queue behind one. Stale for
|
|
# an afternoon beats stale forever, which is what it was.
|
|
#
|
|
# The audit that answers "is this still firing" stays in CLAUDE.md and
|
|
# is one command:
|
|
#
|
|
# ./scripts/issue.sh list --state closed --label "Status/In Progress"
|
|
#
|
|
# A workflow that silently stops working is the failure mode this whole
|
|
# area has already produced once.
|
|
|
|
on:
|
|
issues:
|
|
types: [closed]
|
|
|
|
jobs:
|
|
unclaim:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:24.04
|
|
|
|
steps:
|
|
- name: Drop the claim label
|
|
# **Inside a container the act runner selects `sh`, not bash**, so
|
|
# `set -o pipefail` fails the job on its second line with "Illegal
|
|
# option" and the step never reaches the API. `homebrew-formula.yml`
|
|
# carries the same `set -euo pipefail` without trouble because it
|
|
# runs with **no container**, on the host image where bash is the
|
|
# default — so "another workflow does it" is not evidence here.
|
|
shell: bash
|
|
env:
|
|
# The automatic Actions token, as release.yml uses for the
|
|
# floor tag. It needs no more than write access to this repo.
|
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
|
ISSUE: ${{ github.event.issue.number }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
apt-get update -qq
|
|
apt-get install -y -qq --no-install-recommends curl jq >/dev/null
|
|
|
|
label_id=$(
|
|
curl -sSf -H "Authorization: token $TOKEN" "$API/labels?limit=100" |
|
|
jq -r '.[] | select(.name == "Status/In Progress") | .id'
|
|
)
|
|
|
|
# The label not existing is a repo somebody reorganised, not a
|
|
# failure of this run — say so and stop, rather than failing a
|
|
# job on every close from then on.
|
|
if [ -z "$label_id" ]; then
|
|
echo "unclaim: no 'Status/In Progress' label in this repo; nothing to do"
|
|
exit 0
|
|
fi
|
|
|
|
# DELETE is idempotent here: an issue that never carried the
|
|
# label answers the same as one that did, which is what makes
|
|
# this safe to run on *every* close rather than only the ones
|
|
# that were claimed.
|
|
code=$(
|
|
curl -sS -o /dev/null -w '%{http_code}' -X DELETE \
|
|
-H "Authorization: token $TOKEN" \
|
|
"$API/issues/$ISSUE/labels/$label_id"
|
|
)
|
|
|
|
case "$code" in
|
|
204) echo "unclaim: #$ISSUE is closed and unclaimed" ;;
|
|
*)
|
|
echo "unclaim: DELETE returned $code for #$ISSUE" >&2
|
|
exit 1
|
|
;;
|
|
esac
|