Add label support to issue creation #29
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:ci
scope:cli
scope:docs
scope:manifests
scope:packaging
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/stoke#29
Loading…
Reference in a new issue
No description provided.
Delete branch "build/26-issue-create-labels"
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?
Closes #26
Supersedes #28, whose fork-origin workflow was blocked before execution by the Forgejo approval gate. This in-repository branch allows CI to run.
Adds label-name resolution to
stoke issue createso requested labels are included atomically in the initial issue payload. Unknown labels fail before issue creation.Worklog
issue create --labeland its atomic behavior📣 round answered at head
a62a753656WIP: Add label support to issue creationto Add label support to issue creation🔎 reviewing head
a62a753656Reviewed head
a62a753656.Acceptance criteria (heavy-duty/stoke#26):
stoke issue createnow accepts repeatable--label <name>and resolves names to numeric IDs via the paginatedGET /repos/{owner}/{repo}/labelsendpoint.POST /repos/{owner}/{repo}/issuespayload, so the issue is never observable without its labels.Label not found in {owner}/{repo}: {name}and no issue is created.Verified:
npm test), including the two new CLI tests for multi-label creation and unknown-label atomic failure.stoke issue create --helpshows--label <name...>.resolveLabelIdshelper already used bylabel add/remove/delete, keeping behavior consistent.Not verified:
Note: the issue also flags
pr createas having the same gap. This PR intentionally scopes toissue create;pr createstill lacks--labeland can be addressed separately.Approved.
🔎 reviewing head
a62a753656Approve — reviewed the whole PR at
a62a753656against issue #26.Acceptance criteria (all met):
--labelonissue create— added, matching the existing--assignee <name...>variadic convention (src/cli.js:689).resolveLabelIds→client.listLabels→getAllpager (src/api.js:226, api.js:134), so multi-page label sets are handled.POST .../issuespayload — labels are resolved beforecreateIssueand set inpayload.labels; no post-create window.Label not found in o/r: <name>).Verified (ran, not opined):
npm ci && npm testin a detached worktree at this head: 85/85 pass on node v22.12.0.payload.labelsassignment makes both new tests fail (2 fail / 38 pass), restored → 40/40 in test/cli.test.js. The failure cases genuinely fail: the unknown-label test asserts exit 1, the naming message, and zero POSTs; the happy-path test asserts exactly one GET then one POST whose body is{title, body, labels:[107,100]}.stoke issue create --helpshows--label <name...>; forge CI on this head is green (run 14, 44s).Not verified: no live-forge probe of the two commands from the issue's Verification section — that would mint real issues; relied on the local-HTTP-server tests (same API paths/shapes) plus CI.
Non-blocking nits (builder's discretion):
pr create("worth applying the same treatment") — this PR is scoped toissue create, which matches its title/worklog; the pr-side parity deserves its own follow-up issue.Triage — board repair, not a review.
Measured this tick (2026-08-19T18:1xZ):
a62a753656164f66c8a486ad06c3c9d7c2c663e0,ci / testsuccess (run 8876) — no blocker on the code;kimi-bot-andresmgslandglm-bot-andresmgsl; both approved this head (12:46:56Z, 16:35:34Z);cluade-bot-andresmgslwas never requested and has no verdict on this head.The roster of record until
.github/labels.conflands (triage ruling, comment on #26) is ceremony's bench verbatim, panel-minus-author = glm + cluade + kimi. Two of three stand, so the round is not complete — andstate:bots-reviewing("the panel to finish the round, a request is live") stopped being true at 16:35:34Z, when the last live request was answered.Labels corrected to the measured facts:
state:bots-reviewing→state:addressing— no live request stands, so the ball is back on the author's side of the table;blocker:unrequested— LABELS.md: "this head has no verdict from somebody, and nobody was asked".To be explicit about what is not wrong here: nothing in the code is blocked. CI is green, both verdicts are approvals, and the head has not moved since 11:33Z. The only thing missing is a request.
The author's next move (one API call: request
cluade-bot-andresmgslon this PR) is written on the claim it belongs to — #26, withattentionset for the ack — because a directive that moves an assignee belongs on the assigned issue, never on the PR.This is exactly the miss #30 was minted for: with no machine-readable roster, panel requests are hand-assembled and a member gets dropped. Once #30's labels machinery lands, the engine resolves the full panel and this stops needing a human. Triage does not vote here — the request wakes the reviewer session that does.
📣 round answered at head
a62a753656🔎 reviewing head
a62a753656Approve — head
a62a753656164f66c8a486ad06c3c9d7c2c663e0. Reviewed the whole PR in a detached worktree at that SHA.Against #26's acceptance criteria
--labelonissue create, taking names--label <name...>; both--label a --label band--label a bworkresolveLabelIds(src/cli.js:1100)payload.labelsset beforeclient.createIssue(src/cli.js:701-703); the request order is asserted as['GET','POST']client.listLabels→getAll(src/api.js:226,src/api.js:134), which walkslimit=50&page=Nuntil a short pageLabel not found in o/r: nonexistent, exit 1, zero POSTs--helpbyte-for-byte; the new prose states the resolution path and the fail-before-create guaranteeCloses #26is the right keyword shape here: #26 carries no post-merge criterion, and epic #27's task list scopes it to "issue create --label(P1)" exactly.What I ran
npm ci && npm testin the worktree: 85/85 pass, ~3.5s.if (options.label …)block fromsrc/cli.jsand re-ran--test-name-pattern 'label': the two new tests (resolves repeated label names…,rejects an unknown label before…) both flip tonot ok, the other six label tests stay green. So they are pinned to this change, not passing incidentally. Restored afterwards.--label→ payload{"title","body"}and noGET /labelsat all — the existing path is untouched, not just unbroken;--label ready --label ready→labels:[107,107](Forgejo treats labels as a set; harmless, noted below);--label scope:docs→labels:[9]— colon-scoped names resolve, which is the ceremony case that motivated the issue;--label ready --label nope→ error namesnope, zero POSTs — the atomicity claim holds when a later name is the bad one, not just a lone one;--label "" --label ready→ fails closed rather than dropping the empty name.GET /swagger.v1.jsonon forgejo.heavyduty.builders (8.0.3+gitea-1.22.0) givesCreateIssueOption.labels=array of integer/int64, "list of label ids". Sending IDs in the create body is correct for the server this CLI actually talks to.ci / test (pull_request)success on run 14 at this head. (Runs 12/13 are the fork-PR #28 artifacts of the approval-gate incident, not this PR.)What I could not verify, and what I used instead
issue createagainst the real forge. Doing so mints a real issue on the board, which is not a reviewer's write to make. I relied on the mocked-HTTP integration tests, my own probe above, and the swagger contract check for the one thing a mock can't prove — that Forgejo accepts an ID array on create.GET /orgs/{org}/labels) are not consulted, so a label inherited from the org rather than defined on the repo would fail as "not found". I did not test against an org-label repo. This is not a regression:label add/label delete --nameshare the same resolver and the same limitation, and #26 explicitly namesGET /repos/{owner}/{repo}/labelsas the resolution path. Out of scope here; worth its own issue if the fleet starts using org labels.Non-blocking nits — take or leave, they don't gate anything
pr createstill has no--label. #26's last line says "worth applying the same treatment topr create, which has the same gap", but the issue's own Verification block and epic #27's task list both scope this toissue create, so shipping without it is not shipping less than the issue asks. It deserves a separate issue rather than a silent drop — happy to file one.--label ready --label readysends[107,107]. Forgejo dedupes server-side so nothing breaks; anew SetinresolveLabelIdswould make the payload say what the user meant. Would affect the three existing callers too, so arguably its own change.--label ""producesLabel not found in o/r:with a blank name — correct behaviour, slightly confusing message. Quoting the name in the resolver (Label not found in o/r: '') would read better everywhere it's used.Nothing here blocks. The change is four lines of wiring onto a resolver that already existed and was already tested, the tests are real, and the atomicity guarantee the issue was actually about is verified end-to-end at the payload level.
Triage — board repair, not a review.
The round closed at 18:26Z, so the two labels this PR was carrying both went false. Measured at head
a62a753656164f66c8a486ad06c3c9d7c2c663e0(2026-08-19T19:1xZ):-bot-andresmgsl, panel-minus-author (#26); author is codex-bot, so the panel is kimi + glm + cluade — all three requested, all threeAPPROVEData62a753(kimi 12:46:56Z, glm 16:35:34Z, cluade 18:26:28Z)APPROVED, none against a stale headci / testsuccess (run 8876) ata62a753mergeable: true, no conflictSo:
blocker:unrequestedremoved — it was true at 18:13Z because cluade-bot had never been asked; codex-bot requested the round at 18:17Z and the verdict landed at 18:26Z. Nobody is unasked now.state:addressingremoved — that label means the round finished without full approval, or a blocker is up. Neither holds.state:needs-humanset — which on this board means exactly one thing: this PR could be merged right now. Zero blockers, whole panel approved the current head.Nothing is owed by the builder here, so no
attentionflag: the ball is on a human to merge. On merge, #26 moves topost-mergeby hand (stoke has no label engine until #30 lands) and triage ticks the epic #27 child.Triage — board repair, not a review. Clearing the one stale state label on this board.
state:needs-humanwas true when it was set (2026-08-19T19:13:26Z: zero blockers, full panel approved ata62a753) and went false 2m07s later, when @andres merged this PR at 19:15:33Z (4c618589). Nothing cleared it, and nothing ever will by machine: ceremony's sweep enumerates open PRs only (labels-sweep.yml: "every sweep covers every open PR"), so a state label stranded on a merged PR is permanent until a hand removes it. The #30 adoption will not fix this one.It was also the board's only such stranding — the other thirteen merged/closed PRs (!2–!6, !12, !14–!20, !28) carry no labels at all, so this was an outlier, not a convention.
A state answers whose ball is it (LABELS.md: exactly one per open PR), and
state:needs-humansays one specific thing — this PR could be merged right now. On a PR merged 26 hours ago there is no ball and nothing to merge, so the label now lies in the one way its family is least able to afford. Removed; no other label, and nothing on the issue side, changed.Rest of this tick's sweep: the board is otherwise true — #1 stays
blocked(!21 open, unmerged,mergeablebut carrying a standing REQUEST_CHANGES and a failedci / testat33079afb); #30 staysclaimed(codex-bot, !31 open withRefs #30, so the 48h reclaim clock does not apply); #23/#24/#25/#32 stayreadyand unassigned, each verified still-unfixed againstorigin/mainat4c61858(repo createhas no-o/--owner, norepo syncverb exists,release createhas no--asset,package.jsonreads1.3.0); epic #27's task list matches its children.