release create --asset / release upload — attach assets to a release, and print the release id #25
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#25
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?
Problem
stoke release create(onmain@1.3.0) can create a release with a tag, title and notes, but there is no way to attach a binary asset, and norelease upload/release editsubcommand to add one afterwards.Impact
A release whose payload is an installer or bundle cannot be mirrored with stoke alone. Mirroring
github.com/heavy-duty/crewreleases0.1.0and0.1.1— each a singlecrew-<version>.shof 29 MB / 50 MB — needed stoke for the release object and then a raw API call per asset:Note the extra round-trip: the asset endpoint is keyed by numeric release id, which
release createdoes not print, so callers must re-query by tag to get it. Printing the id on create would help even if asset upload lands separately.Proposed fix
--asset <path>torelease create, uploading each file after the release is created:POST /api/v1/repos/{owner}/{repo}/releases/{id}/assets?name=<basename>(multipart fieldattachment).stoke release upload -o … -r … --tag <tag> --asset <path>...for attaching to an existing release.--asset-name <name>to override the uploaded filename, and include the releaseidinrelease createoutput.Verification
Related: #23 (no
repo sync) and therepo create --ownergap — all three surfaced while mirroring a GitHub repo onto the forge.Brought up to the issue contract by triage, 2026-08-20. The "Proposed fix" above listed options; the spec below decides them — in particular the release id, which epic #27 already folded into this issue's scope ("
release create --asset+ print the release id"). Nothing above was changed. Code references pinned at4c61858.Spec
Decisions, not options:
release creategains a repeatable--asset <path>. Each file is uploaded after the release object exists, viaPOST /repos/{owner}/{repo}/releases/{id}/assets?name=<basename>, multipart fieldattachment.release upload -o … -r … --tag <tag> --asset <path>...is a new subcommand for attaching to an existing release. It resolves the tag to an id with thereleases/tags/{tag}lookupgetReleaseByTag()already implements.--asset-name <name>is in scope, not optional. It overrides the uploaded filename and is only legal when exactly one--assetis given; with two or more it is a usage error, because one name cannot serve many files.release createprints the numeric releaseidon success, alongside the tag and URL, so callers never need the re-query round-trip this issue documents.release upload.request()hardcodesContent-Type: application/jsonandJSON.stringifys the body, so it cannot carry a file. Add a distinct upload path that sets no manualContent-Type(lettingFormData/fetchset the boundary) and streams the file rather than buffering it — crew's0.1.0/0.1.1payloads are 29 MB and 50 MB.REQUEST_TIMEOUT_MS = 30000would abort a 50 MB push on any ordinary link; asset uploads take their own, longer timeout.Tasks
src/api.jswith its own timeout, plus anuploadReleaseAsset(owner, repo, id, path, name)method--assetand single-use--asset-nametorelease create; print the releaseidon successrelease uploadsubcommand (tag → id resolution, repeatable--asset)release uploadonto an existing tag,--asset-namewith one asset,--asset-namewith two assets rejected, upload failure keeps the release and exits non-zero,Content-Typeis not forced to JSON on the upload path--asset,--asset-name,release upload, and the printed idheavy-duty/stoke, not a fork (fork PRs stall on the CI approval gate — see !28/!29)state:needs-human, request@andresby hand — the engine's own request 404s on this forge and its sweep log reports that failure as a success (#36, defect 1)Acceptance criteria
release create … --asset a --asset bcreates the release and attaches both;release view --tag …lists both with correct sizesrelease createprints the numeric release id on successrelease upload --tag <existing> --asset fattaches to a release created earlier, without recreating it--asset-namerenames a single asset, and is rejected with a usage error and non-zero exit when two or more--assetare given — the case that must failTest plan
Suite-level, against a stubbed HTTP layer: assert the upload request carries a multipart body and no hand-set
application/jsoncontent type, that the id-resolution round-trip happens exactly once perrelease upload, and each rejection case above. Manual proof against the live instance:Dependencies
Part of #27 (P2). No blockers. Related: #23 and #24 — all three surfaced while mirroring a GitHub repo onto the forge, and all three touch
src/cli.js. #32 (release 1.4.0) is not gated by this issue and no longer names it as a risk: its "Create release and attach .deb" step attaches with a rawcurl -F attachment=@…, not withstoke release create, so the automated release path never touches the missing asset support (measured 2026-08-20; #32's body was corrected the same day). This gap bites only a hand-finished release.feature: release create cannot upload assetsto release create --asset / release upload — attach assets to a release, and print the release idTriage: brought up to contract, 2026-08-20. The "Proposed fix" above listed three items, one of them explicitly optional — an issue whose spec still lists options is not
ready, whatever the label said. The spec below the rule decides them; nothing above the rule was changed, and the title now names the deliverable.What got decided:
--asset-nameand printing the releaseidare in scope, not optional — epic #27 already folded the id into this issue ("release create --asset+ print the release id").--asset-nameis legal only with a single--asset; two or more is a usage error.Two implementation facts found while specifying, both pinned at
4c61858, because they are the actual work and the issue did not mention either:request()hardcodesContent-Type: application/jsonandJSON.stringifys the body. There is no multipart path today; one has to be added, and it must stream rather than buffer — the crew payloads that motivated this are 29 MB and 50 MB.REQUEST_TIMEOUT_MS = 30000would abort a 50 MB upload on any ordinary link, so the upload path takes its own timeout. There is an acceptance criterion for exactly that, so the requirement is falsifiable rather than decorative.Unrelated but worth recording here: #32 (release 1.4.0) lists this issue as a risk to its own release step. It is not one —
release.ymlattaches its.debwith a rawcurl -F, not withstoke release create. Noted on #32.Triage — Dependencies line corrected, 2026-08-20. No spec, task, criteria, or label change.
This issue's Dependencies said "#32 (release 1.4.0) names this issue as a risk to its own asset step." That described #32 as it read when this line was written; #32's body was corrected on 2026-08-20 and now says the opposite, with the measurement:
release.yml's "Create release and attach .deb" step attaches the asset with a rawcurl -F attachment=@…, not withstoke release create, so the missing CLI upload support never touches the automated release path. It bites only a hand-finished release.The conclusion both issues reach is unchanged — this issue does not block #32 — but a builder following the old wording would have gone to #32 looking for a live risk note and found a refutation instead. Both directions of the edge now agree.
Triage — collision edge added,
ready→blocked, 2026-08-20. No spec, task,acceptance criterion or estimate changed. This is an ordering edge only: nothing here
waits on #24's content, and when #24 closes this issue flips back to
readyunchanged.The finding. #23, #24 and #25 all carried
readyfrom a single batch write on2026-08-18T00:23:52Z, and none of the three declared a collision edge — so all three were
concurrently claimable. Each body already said "all three touch
src/cli.js", but that wasrecorded as Related prose and never as a dependency, so it constrained nothing.
src/cli.jsis a single 1653-line file in which every command group is chained(
repoat L326,releaseat L985). #24 editsrepo create(L424) andcreateRepo()insrc/api.js; #25 editsrelease create(L1057) and addsrelease uploadplus theasset-upload path in
src/api.js; #23 adds arepo syncsubcommand inside the samerepogroup #24 modifies and reusesgitAuthEnv(). All three also add a section toREADME.md.The fleet's builders pick
ready+ unassigned. Two simultaneous claims were therefore alive possibility, and the second PR to open would have contended with the first over the
same file. TRIAGE.md's collision rule
is written for exactly this: a deliverable already carried by an open
ready/claimed/blockedissue takes an unconditional edge, and disjoint regions inside the shared files are
explicitly not an exemption.
Order chosen: #24 (P1) → #25 (P2) → #23 (P3) — epic #27's own declared priority, not
mint order. Mint order (#23 → #24 → #25) would have gated both smaller, higher-priority
issues behind the largest one, inverting the epic. #24 stays
readyas the cluster's singlesource; each close now releases exactly one successor.
Verified with ceremony's own parser (
lib/issue_references.sh+blocked_reference_records)against the live bodies: this issue parses to
{#24}, #23 to{#25}, #24 to{}.Recorded as finding 3 on epic #27.
Triage — retracted, 2026-08-20. The collision edge above was wrong; this issue is
readyagain and claimable now.Blocked by #24has been removed from the body and theDependencies section restored verbatim. No spec, task or criterion was ever touched.
Why the edge was wrong. The collision rule
(#288) governs one deliverable carried by two issues, not any two issues that touch one
large file, and its "disjoint regions do not waive it" clause means regions of that shared
deliverable. I read "all three touch
src/cli.js" as the collision and it is not one.The sweep that enforces the rule settles it:
deliverable_key(issueflow-reconcile 0.6.1,L433) keys on the title's em-dash prefix, normalized and case-folded. The three keys are
repo sync(#23),repo create --owner(#24) andrelease create --asset / release upload(#25) — three distinct deliverables, so the collision flag would not fire on this trio, and
it would be right not to.
Ordinary same-file contention between distinct deliverables is a rebase — the builder's
normal cost, already carrying its own PR label (
blocker:conflict). It is not a board gate,and making it one would have left two smaller, higher-priority issues unclaimable behind the
largest for no doctrinal reason.
Net effect on this issue: none. It is exactly as it was before my previous comment —
ready, unassigned, no blockers, claimable today. Recorded as finding 3 on epic #27 so thequestion is not re-opened from scratch.
Starting work on #25 as @codex-bot-andresmgsl.
Design / plan of record:
ForgejoClientwith an upload-only request path that acceptsFormData, leavesContent-Typeto fetch, streams files from disk, and uses a longer upload timeout without changing JSON requests.uploadReleaseAsset(owner, repo, releaseId, path, name); keep release lookup/creation in the existing API methods.--assetplus single-asset--asset-nametorelease create, print the release id, and account for each upload so partial success is reported honestly while the created release remains.release uploadusing one tag-to-id lookup and the same upload orchestration.## Worklogthrough signal-then-ready handoff.codex-bot-andresmgsl referenced this issue2026-08-30 09:48:36 +00:00
glm-bot-andresmgsl referenced this issue2026-08-30 10:57:23 +00:00
claude-bot-andresmgsl referenced this issue2026-08-30 12:00:59 +00:00
Triage, 2026-08-31T16:39Z — stale
claimedreleased on a closed issue. No other change.This issue closed at 2026-08-30T13:12:37Z on !37's
Closes #25, which closes the issue but never releases the claim: the derived claim→post-mergetransition is built only fromRefs #Nreferences, andissueflow-reconcileenumeratesissues?state=open(L1380), so nothing the machine runs ever looks at a closed issue's labels again. The label has been asserting an active claim ever since.claimedremoved. The assignee stays as build attribution. Nothing is owed on this issue and its close is not disturbed.