feat(single-stack): port setup-bump-labels.sh to rt setup-bump-labels #776

Closed
opened 2026-08-20 08:31:50 +02:00 by bosun · 1 comment
Owner

Motivation

scripts/setup-bump-labels.sh86 lines — creates the bump/{patch,minor,major} labels on
a Forgejo repo as an exclusive group (Forgejo then enforces single-select, so picking
bump/minor auto-removes bump/patch). It is idempotent: it skips labels already correct and
updates ones missing the exclusive flag.

It is one of five bash files left on main (1,789 lines total) and it is the clearest port of
the remainder.
Everything it does is already Go's domain here: argument parsing, a GET /labels read, conditional POST/PATCH, and a comparison. The Forgejo client that #705 part
B lands makes this nearly mechanical.

🔑 It is ADOPTER-FACING, which decides where it lands on the CLI surface

Referenced from docs/integration.md and three architecture documents — never from a
workflow
. It is a setup verb an adopter runs once against their own repo, not toolkit-self
machinery.

So it belongs in the adopter half of the surface, unlike check-self-bootstrap and
binary-size-check
(see #774). Porting it makes the adopter verb count 17, not 16 — and
that is a real change to the C5 contract, not a bookkeeping one.

The port REMOVES a bug class rather than relocating one

The script hand-rolls keeping the token out of curl's argv (release-toolkit#371):

AUTH_CONFIG=$(mktemp)
install -m600 /dev/null "$AUTH_CONFIG"          # 0600 from birth, no umask race
trap 'rm -f "$AUTH_CONFIG"' EXIT
printf 'header = "Authorization: token %s"\n' "$FORGEJO_TOKEN" > "$AUTH_CONFIG"

In Go the token is a field on a request object and never becomes a process argument, so the
temp-file dance, the mode, the trap and every path that could skip the trap all cease to
exist.
That is a structural close, not a reimplementation.

⚠️ It also carries a duplicated guardFORGEJO_TOKEN is checked for emptiness twice,
at two different lines, with two different message formats. Harmless, and a tell that the file
has been edited by more than one pass.

Scope

  • rt setup-bump-labels OWNER/REPO, adopter-facing, registered in the adopter half of main.go's
    subcommands slice
  • Preserve idempotence and its three distinguishable outcomes: created · updated (exclusive
    flag was missing) · already correct. Do not collapse them into one success line — see
    /srv/CLAUDE.md §a scope note inside a success message is decoration
  • Preserve the exclusive-group semantics; read_rolling_pr_bump_label's defence-in-depth guard
    against multiple bump labels stays as it is
  • FORGEJO_BASE_URL default and FORGEJO_TOKEN requirement unchanged
  • Delete scripts/setup-bump-labels.sh; update docs/integration.md and the three architecture
    references in the same PR

Acceptance criteria

  • rt setup-bump-labels OWNER/REPO creates the three labels as an exclusive group on a repo that has none
  • Re-running against a correct repo reports already correct and mutates nothing
  • A repo whose bump/* labels exist WITHOUT the exclusive flag is updated, and that outcome is distinguishable from both others
  • The token never appears in any process argument — asserted, not assumed
  • scripts/setup-bump-labels.sh is deleted and no doc still points at it
  • The C5 contract (docs/architecture/contracts/cli-surface.md) reflects the new adopter verb
  • #705 — retire the last bash (forgejo-api.sh); lands the Forgejo client this depends on
  • #774 — the subcommand slice is a superset of the C5 contract; this adds an ADOPTER verb, which is the opposite direction from the two toolkit-self verbs in dispute there
  • #371 — token-out-of-argv, the guard this port makes unnecessary
  • #107 — the label-based bump-control mechanism itself

Anchor

2026-08-20 09:23, operator scoping of the bash-retirement remainder: "setup-bump-labels.sh looks
like it has some logic and some curl calls for web access. Both are in the domain of Go, I
guess?"
— correct, and the adopter-facing finding is what decides its placement. Census measured
recursively against origin/main: 5 files, 1,789 lines.

## Motivation `scripts/setup-bump-labels.sh` — **86 lines** — creates the `bump/{patch,minor,major}` labels on a Forgejo repo as an **exclusive** group (Forgejo then enforces single-select, so picking `bump/minor` auto-removes `bump/patch`). It is idempotent: it skips labels already correct and updates ones missing the exclusive flag. **It is one of five bash files left on `main` (1,789 lines total) and it is the clearest port of the remainder.** Everything it does is already Go's domain here: argument parsing, a `GET /labels` read, conditional `POST`/`PATCH`, and a comparison. The Forgejo client that `#705` part B lands makes this nearly mechanical. ### 🔑 It is ADOPTER-FACING, which decides where it lands on the CLI surface Referenced from `docs/integration.md` and three architecture documents — **never from a workflow**. It is a *setup* verb an adopter runs once against their own repo, not toolkit-self machinery. **So it belongs in the adopter half of the surface, unlike `check-self-bootstrap` and `binary-size-check`** (see `#774`). Porting it makes the adopter verb count **17**, not 16 — and that is a real change to the C5 contract, not a bookkeeping one. ### ✅ The port REMOVES a bug class rather than relocating one The script hand-rolls keeping the token out of `curl`'s `argv` (`release-toolkit#371`): ```bash AUTH_CONFIG=$(mktemp) install -m600 /dev/null "$AUTH_CONFIG" # 0600 from birth, no umask race trap 'rm -f "$AUTH_CONFIG"' EXIT printf 'header = "Authorization: token %s"\n' "$FORGEJO_TOKEN" > "$AUTH_CONFIG" ``` **In Go the token is a field on a request object and never becomes a process argument, so the temp-file dance, the mode, the trap and every path that could skip the trap all cease to exist.** That is a structural close, not a reimplementation. ⚠️ **It also carries a duplicated guard** — `FORGEJO_TOKEN` is checked for emptiness **twice**, at two different lines, with two different message formats. Harmless, and a tell that the file has been edited by more than one pass. ## Scope - `rt setup-bump-labels OWNER/REPO`, adopter-facing, registered in the adopter half of `main.go`'s `subcommands` slice - Preserve idempotence **and its three distinguishable outcomes**: created · updated (exclusive flag was missing) · already correct. Do not collapse them into one success line — see `/srv/CLAUDE.md` §*a scope note inside a success message is decoration* - Preserve the exclusive-group semantics; `read_rolling_pr_bump_label`'s defence-in-depth guard against multiple bump labels stays as it is - `FORGEJO_BASE_URL` default and `FORGEJO_TOKEN` requirement unchanged - Delete `scripts/setup-bump-labels.sh`; update `docs/integration.md` and the three architecture references in the same PR ## Acceptance criteria - [x] `rt setup-bump-labels OWNER/REPO` creates the three labels as an exclusive group on a repo that has none - [x] Re-running against a correct repo reports *already correct* and mutates nothing - [x] A repo whose `bump/*` labels exist WITHOUT the exclusive flag is updated, and that outcome is distinguishable from both others - [x] The token never appears in any process argument — asserted, not assumed - [x] `scripts/setup-bump-labels.sh` is deleted and no doc still points at it - [x] The C5 contract (`docs/architecture/contracts/cli-surface.md`) reflects the new adopter verb ## Related - `#705` — retire the last bash (`forgejo-api.sh`); lands the Forgejo client this depends on - `#774` — the subcommand slice is a superset of the C5 contract; this adds an ADOPTER verb, which is the opposite direction from the two toolkit-self verbs in dispute there - `#371` — token-out-of-argv, the guard this port makes unnecessary - `#107` — the label-based bump-control mechanism itself ## Anchor 2026-08-20 09:23, operator scoping of the bash-retirement remainder: *"setup-bump-labels.sh looks like it has some logic and some curl calls for web access. Both are in the domain of Go, I guess?"* — correct, and the adopter-facing finding is what decides its placement. Census measured recursively against `origin/main`: 5 files, 1,789 lines.
bosun closed this issue 2026-08-20 09:39:08 +02:00
Author
Owner

ACs ticked — verified, including the two that a passing test suite does not establish

script deleted                        MET  scripts/setup-bump-labels.sh absent from main
rt subcommand registered              MET  cmd/rt/main.go carries "setup-bump-labels"
C5 contract reflects the adopter verb MET  cli-surface.md names it
three distinguishable outcomes        MET  setup_bump_labels.go:95/100/111 — created /
                                           "updating %s to set exclusive=true" / "already
                                           configured - skip". Three branches, three messages.
token never in a process argument     MET  0 exec-shaped calls in the file; the token travels
                                           as a request header via internal/forgejo

🔑 The last one is the AC the port EXISTS to satisfy and it is now structural rather than
disciplined. The bash hand-rolled mktemp + install -m600 + trap to keep the token out of
curl's argv (#371). In Go there is no argv to keep it out of — 0 exec calls, so the whole
class is unreachable rather than merely avoided.

📌 The idempotence and exclusive-group ACs rest on @pilot's 7 tests plus @surveyor's
byte-identical label-value differential against the bash — which she ran before the merge
deleted the source of truth she was comparing against.
That comparison is no longer possible;
it was run at the only moment it could be.

## ACs ticked — verified, including the two that a passing test suite does not establish ``` script deleted MET scripts/setup-bump-labels.sh absent from main rt subcommand registered MET cmd/rt/main.go carries "setup-bump-labels" C5 contract reflects the adopter verb MET cli-surface.md names it three distinguishable outcomes MET setup_bump_labels.go:95/100/111 — created / "updating %s to set exclusive=true" / "already configured - skip". Three branches, three messages. token never in a process argument MET 0 exec-shaped calls in the file; the token travels as a request header via internal/forgejo ``` 🔑 **The last one is the AC the port EXISTS to satisfy** and it is now structural rather than disciplined. The bash hand-rolled `mktemp` + `install -m600` + `trap` to keep the token out of `curl`'s argv (`#371`). In Go there is no argv to keep it out of — **0 exec calls**, so the whole class is unreachable rather than merely avoided. 📌 The idempotence and exclusive-group ACs rest on @pilot's 7 tests plus @surveyor's byte-identical label-value differential against the bash — **which she ran before the merge deleted the source of truth she was comparing against.** That comparison is no longer possible; it was run at the only moment it could be.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#776
No description provided.