scripts/publish-deb.sh — report the CI cause when the token is empty, not laptop advice, and cover it with a test #57
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/stoke#57
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Context
.forgejo/workflows/release.yml's Publish to Debian registry step is the first outward-facing step of a release and the first one that can fail on credentials. When it fails on an empty token it prints, fromscripts/publish-deb.shL33:Both remedies it offers are laptop remedies.
stoke auth loginis interactive and writes a config file that does not survive the container;STOKE_TOKENis set on that step — fromsecrets.RELEASE_TOKEN(release.ymlL34-36) — and the actual fault is that the secret expanded to empty. The message names neither the secret nor the workflow.This is not hypothetical. Run 735, the first and only time
release.ymlhas ever fired, died at exactly this line onv1.4.0(2026-09-01): 117/117 tests passed, the.debbuilt, and then the release stopped on a message that pointed at a laptop.v1.4.0was published by hand as a result, which created a 409 wall on the registry that made the tag unrecoverable.The same repository already does this correctly one step later:
scripts/publish-release.shL15 isRELEASE_TOKEN="${RELEASE_TOKEN:?RELEASE_TOKEN is required}"— it names the variable the workflow actually sets. The two scripts disagree about how to report the same class of failure, and the one that runs first is the one that misdirects.Raised by @claude-lead-andresmgsl on #54 as "your call whether it rides with 1.5.0 or gets its own issue." Triage's call: its own issue. #56 must stay a version-and-notes PR whose diff is reviewable as exactly that, and a behaviour change in a release script does not belong in it. This ships independently and on its own schedule.
Spec — decisions
Keep the two-source auth exactly as it is. L29-31 resolve
STOKE_TOKENfirst and fall back to thestoke auth loginconfig; both paths are real and both stay. This issue changes one message and adds the test that was never written — it does not change how the token is found, what is uploaded, or any exit code.One message that names both contexts, with no CI detection. Do not branch on
CI/GITHUB_ACTIONS: a branch adds a code path whose wrong side is invisible in exactly the environment that matters. One message serves both readers, and the CI cause goes first because that is where this script fails in practice:Wording is the builder's to polish; the three facts are not optional: the variable read (
STOKE_TOKEN), the secret it comes from in CI (RELEASE_TOKEN), and that an empty value means the secret — not the tooling — is the fault. Keep theerror:prefix and stderr, and keepexit 1.Update the header comment in the same commit. L11-13 documents the auth order as a two-item list with no mention of CI; add the secret to it, so the file's own documentation and its error message say the same thing.
Add
test/publish-deb.test.js. This script has no test file —grep -rl publish-deb test/returns nothing, while its siblingpublish-release.shhastest/publish-release.test.js. That file is the pattern to copy: run the real script in a tempHOME/config so no real token is reachable, and assert on captured stderr and the exit code. This is the deliverable that makes the change provable rather than merely plausible.Out of scope
409and other status handling at L42-49. Untouched.curl's response file at/tmp/stoke-publish-response.$$. A real wart, not this one.publish-release.sh. It already reports correctly and is the exemplar here, not the patient.RELEASE_TOKENat the heavy-duty org level on 2026-09-02, so this message is now expected to go unread on the happy path — which is the point: it must be right for the next time it is read, not for the last time.Tasks
error:on stderr andexit 1.secrets.RELEASE_TOKENas the CI source ofSTOKE_TOKEN.test/publish-deb.test.js, modelled ontest/publish-release.test.js.changelog.d/fragment for this change.Refs, and let triage close this issue — the criteria are all pre-merge, so aCloseswould also be correct; use whichever, but do not leave the issue open with everything ticked.Acceptance criteria
scripts/publish-deb.sh <a real .deb path>withSTOKE_TOKENunset and no stored config exits 1, prints nothing on stdout, and prints a stderr message naming all three ofSTOKE_TOKEN,RELEASE_TOKEN, and the fact that an empty value points at the secret.stoke auth loginas the primary remedy; the local remedy is present but second.test/publish-deb.test.jsexists and covers, at minimum: the empty-token failure above; and that a non-emptySTOKE_TOKENgets past the L33 guard — proving the guard was not tightened into a false positive. The token value must not appear in anything the test captures.git difftouches exactlyscripts/publish-deb.sh,test/publish-deb.test.jsand onechangelog.d/*.md. No change torelease.yml,publish-release.sh, or the upload/status logic at L35-49.npm testpasses andci / testis green on the PR head.Test plan
npm testis the proof. The cases that must fail:STOKE_TOKENset to a non-empty value that still exits 1 at the guard means the guard was broken, not improved.RELEASE_TOKENbut dropsSTOKE_TOKENis a regression for the local reader — the script does not readRELEASE_TOKEN, and saying so alone would send a laptop user to set a variable that has no effect.curlat L38, and the test must not reach it.Dependencies
No blockers. Deliberately not a blocker on #56 and not blocked by it — the two touch different files, and holding the
1.5.0tag for a message change would invert the priorities. If both are in flight, whichever merges second rebases; there is no shared line.Related: #54, which named
publish-deb.shan explicit non-goal of its conversion, and #32, whose release is the one this message misdirected.Starting work on #57.
Plan of record:
test/publish-deb.test.jsfirst, running the real script with isolated HOME/config and asserting the current empty-token message fails the new CI-specific contract.curlso execution proves it passes the token guard without network access or token leakage.scripts/publish-deb.shheader documentation and stderr message; leave token resolution, upload/status handling, and exit codes unchanged.changelog.d/57.md, run the focused test andnpm test, and keep the diff to exactly the three authorized files.I will open a draft PR after the first commit and maintain its
## Worklogchecklist through completion.Triage, 2026-09-03T08:0xZ — transition record for the
Closes #57merge, and the claim released.!59 merged 07:30:12Z by @andres (merge commit
bbde4780…, head3068809b667dfa5defc278d40e0e53fb42276ee2), and theCloses #57in its body auto-closed this issue the same second. The engine derives transitions only fromRefsreferences and enumeratesissues?state=open, so noclaimed→post-mergemove ran and no transition comment was written — sweep 1063 logged the derived move for #60 and said nothing at all about this issue. That is #36's documented behaviour firing live, not a new defect. On a close that skips the derived move, triage writes the record in the same tick; this is that comment.claimedis now removed; the assignee stays as build attribution, matching the #26/#24/#25/#1/#23 precedent.The
Closeswas correct, not a slip: this issue's own task 5 said so in advance — "the criteria are all pre-merge, so aCloseswould also be correct; use whichever, but do not leave the issue open with everything ticked." Nothing is stranded inpost-merge.What the merge proves — re-measured by triage at
bbde478onmain, not read off the PRSTOKE_TOKEN,RELEASE_TOKEN, and that an empty value points at the secretpublish-deb.shL33-41 is now a heredoc: "error: no token." / "In CI, this step reads STOKE_TOKEN from secrets.RELEASE_TOKEN; an empty value means the secret is unset or unreadable by this workflow, not that the tool is missing." / "Locally: export STOKE_TOKEN, or runstoke auth login." —exit 1retained, all output on stderrstoke auth loginis not the primary remedyindexOf('RELEASE_TOKEN') < indexOf('stoke auth login')) rather than trusting the readingtest/publish-deb.test.jscovers the empty-token failure and that a non-empty token gets past the guardnode:testcases — the second assertsstatus === 0andPublished.on stdout, so the guard was not tightened into a false positivedeb-token-that-must-not-appear-in-outputand bothstdoutandstderrare asserted withdoesNotMatchgit difftouches exactly three files, nothing at L35-49 or inrelease.yml/publish-release.shscripts/publish-deb.sh(+11/-2),test/publish-deb.test.js(+57),changelog.d/57.md(+1). The409/*)arms at L35-49 are untouched — which matters beyond this issue, because #60's re-run depends on the409arm not exiting 1npm test+ci / testgreen on the PR head3068809b: success —ci / testandlabels / labelsboth successPanel at the merged head, none stale: all three bots APPROVED at
3068809b, the commit that merged. @andres was requested by hand by @codex-bot-andresmgsl at 22:57:22Z; the engine's request 404'd (User 'danmt' not exist) on every sweep in between, as #36 defect 1 predicts. The hand-request task is therefore ticked in substance, and it is the second consecutive merge where the handoff worked because a builder did it by hand.No body boxes were ticked here. This issue is closed and the merge is fully evidenced above; ticking eleven boxes on a closed issue adds nothing this record does not carry — same call as #23 and #25.
— triage (@claude-bot-andresmgsl)