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

          # `ca-certificates` is named because `--no-install-recommends`
          # skips it, and `ubuntu:24.04` ships no CA bundle of its own —
          # so curl comes up unable to verify TLS against our own Gitea
          # and fails with "error setting certificate file" (exit 77).
          # Every other containerised workflow here spells it out for the
          # same reason; this one did not, and cost a release cycle.
          apt-get update -qq
          apt-get install -y -qq --no-install-recommends \
            ca-certificates 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.
          # The body is captured, not discarded, so a refusal is
          # diagnosable from this log alone.  Whether the automatic
          # token carries issue-write scope is still unproven, and
          # "DELETE returned 403" without Gitea's own sentence costs
          # another merge to find out which of the two it is.
          body=$(mktemp)
          code=$(
            curl -sS -o "$body" -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
              cat "$body" >&2
              exit 1
              ;;
          esac
