Skip to content

Commit 2f119e0

Browse files
jmclaren7claude
andcommitted
feat(ci): publish desktop artifacts as pruned dev releases
The Desktop Artifacts workflow built unsigned binaries but only left them as workflow artifacts with 14-day retention, so there was no stable place to grab the latest development build. Add a release job that publishes every run's artifacts as a GitHub prerelease tagged desktop-dev-<run number> (a development build, never marked latest) and prunes older desktop-dev-* releases, keeping the current one plus two. The job uses only the automatic GITHUB_TOKEN with job-scoped contents: write, staying inside the fork's standing workflow rule. FORK.md entry 14, the README banner, and the CI/release docs now record the publish-and-prune goal so it carries through future rebases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018LD1a6B2Xn9A45NWfWjd9B
1 parent e6845ad commit 2f119e0

5 files changed

Lines changed: 121 additions & 10 deletions

File tree

‎.github/workflows/desktop-artifacts.yml‎

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Fork workflow: build the desktop binaries for every platform this repo already
2-
# supports, on every push to `main`.
2+
# supports, on every push to `main`, and publish them as a development-build
3+
# prerelease (`desktop-dev-<run number>`), pruning older dev releases so only
4+
# the current one plus two remain.
35
#
46
# Upstream produces these from `release.yml`, which also reads the production
57
# T3 Connect relay config, signs, publishes to npm, and cuts a GitHub Release —
@@ -8,7 +10,8 @@
810
# fork can actually run.
911
#
1012
# Constraints this file deliberately respects:
11-
# - no secrets beyond the automatic GITHUB_TOKEN (which it never uses)
13+
# - no secrets beyond the automatic GITHUB_TOKEN (only the release job uses
14+
# it, with job-scoped contents: write, to publish and prune dev releases)
1215
# - standard GitHub-hosted runners only (upstream's `blacksmith-*` labels do
1316
# not resolve here, so those jobs sat queued for 24h and were cancelled)
1417
# - builds are unsigned: passing `--signed` is what pulls in signing
@@ -226,3 +229,94 @@ jobs:
226229
path: release/*
227230
if-no-files-found: error
228231
retention-days: 14
232+
233+
release:
234+
name: Publish dev release
235+
# Publish whatever platforms built — a development build with one platform
236+
# missing is still useful, and the build job is what turns the run red.
237+
# The publish step fails only when no platform produced anything at all.
238+
needs: [build]
239+
if: ${{ !cancelled() }}
240+
runs-on: ubuntu-24.04
241+
timeout-minutes: 30
242+
permissions:
243+
contents: write
244+
env:
245+
GH_TOKEN: ${{ github.token }}
246+
TAG_PREFIX: desktop-dev-
247+
KEEP_RELEASES: "3"
248+
steps:
249+
- name: Download desktop artifacts
250+
uses: actions/download-artifact@v8
251+
with:
252+
pattern: desktop-*
253+
path: assets
254+
255+
# Each artifact lands in its own directory; flatten them for upload. The
256+
# installer names carry the arch (T3-Code-<version>-<arch>.<ext>), but
257+
# electron-builder's update metadata does not — both macOS legs emit a
258+
# latest-mac.yml — so a colliding name gets its artifact name prefixed.
259+
- name: Collect release assets
260+
shell: bash
261+
run: |
262+
set -euo pipefail
263+
shopt -s nullglob
264+
mkdir -p dist
265+
for dir in assets/*/; do
266+
artifact="$(basename "$dir")"
267+
for file in "$dir"*; do
268+
base="$(basename "$file")"
269+
if [[ -e "dist/$base" ]]; then
270+
base="${artifact}-${base}"
271+
fi
272+
mv "$file" "dist/$base"
273+
done
274+
done
275+
ls -lh dist
276+
277+
- name: Create development release
278+
shell: bash
279+
run: |
280+
set -euo pipefail
281+
shopt -s nullglob
282+
files=(dist/*)
283+
if (( ${#files[@]} == 0 )); then
284+
echo "::error::No desktop artifacts were produced; nothing to release."
285+
exit 1
286+
fi
287+
288+
tag="${TAG_PREFIX}${GITHUB_RUN_NUMBER}"
289+
# A re-run reuses the run number, so drop any release this same run
290+
# already published before recreating it.
291+
gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --yes --cleanup-tag || true
292+
293+
{
294+
echo "Unsigned development build from \`${GITHUB_SHA}\`."
295+
echo
296+
echo "Published automatically by the Desktop Artifacts workflow"
297+
echo "([run ${GITHUB_RUN_NUMBER}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}))."
298+
echo "Older development releases are pruned; only the newest ${KEEP_RELEASES} are kept."
299+
} > notes.md
300+
301+
gh release create "$tag" "${files[@]}" \
302+
--repo "$GITHUB_REPOSITORY" \
303+
--target "$GITHUB_SHA" \
304+
--title "Desktop dev build ${GITHUB_SHA::7}" \
305+
--prerelease \
306+
--notes-file notes.md
307+
308+
- name: Prune older development releases
309+
shell: bash
310+
run: |
311+
set -euo pipefail
312+
current="${TAG_PREFIX}${GITHUB_RUN_NUMBER}"
313+
gh api "repos/${GITHUB_REPOSITORY}/releases" --paginate \
314+
--jq ".[] | select(.tag_name | startswith(\"${TAG_PREFIX}\")) | [.created_at, .tag_name] | @tsv" |
315+
sort -r | tail -n +"$((KEEP_RELEASES + 1))" | cut -f2 |
316+
while IFS= read -r tag; do
317+
if [[ "$tag" == "$current" ]]; then
318+
continue
319+
fi
320+
echo "Pruning $tag"
321+
gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --yes --cleanup-tag
322+
done

‎FORK.md‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ possible and changes are reapplied based on intent, not directly based on the sp
1111
existing implementation. In practice that thin layer has converged on one substantive thing —
1212
**a CI/workflow set a fork can actually run** (standard GitHub-hosted runners instead of
1313
upstream's Blacksmith ones, nothing needing credentials a fork lacks, and unsigned desktop
14-
artifacts built on every push to `main`) — plus this file and the `README.md` fork banner that
14+
artifacts built on every push to `main` and published as pruned development-build
15+
prereleases) — plus this file and the `README.md` fork banner that
1516
points at it. Alongside it the fork carries the three multi-instance provider changes
1617
(entries 15, 16 and 19), a configurable worktree branch prefix (entry 17), one sidebar layout
1718
change (entry 18), and three web UX changes re-derived at the 2026-08-22 rebase (entries 5, 6
@@ -355,8 +356,15 @@ apps/web/src/components/Sidebar.logic.test.ts` (128 passed, 4 new: acknowledgeme
355356
.github/scripts/thread-transfer-report.test.cjs` passes 6/6 here.
356357
- **Added:** `desktop-artifacts.yml` — builds the four platforms upstream's
357358
`release.yml` matrix covers (macOS `arm64`/`x64` DMG, Linux `x64` AppImage,
358-
Windows `x64` NSIS) on every push to `main` and on dispatch, **unsigned**, and
359-
uploads them as workflow artifacts. Carries over the three secret-free steps that
359+
Windows `x64` NSIS) on every push to `main` and on dispatch, **unsigned**,
360+
uploads them as workflow artifacts, and then **publishes every run as a GitHub
361+
prerelease** tagged `desktop-dev-<run number>` — always a prerelease, never
362+
"latest", i.e. a development build — **pruning older `desktop-dev-*` releases so
363+
only the current one plus two remain**. The release job is the one place the
364+
automatic `GITHUB_TOKEN` is used (job-scoped `contents: write`), which stays
365+
inside the standing rule; it publishes whatever platforms built, fails only when
366+
none did, and prefixes an asset with its artifact name on a filename collision
367+
(both macOS legs emit a `latest-mac.yml`). Carries over the three secret-free steps that
360368
matter from upstream's build job: the `dtolnay/rust-toolchain` setup with a per-matrix
361369
`rust_target` (upstream's desktop build cargo-builds `native/resource-monitor`, #2679),
362370
the Linux `node-pty` prebuild bundled into the Windows artifact (non-fatal when missing),
@@ -407,7 +415,11 @@ apps/web/src/components/Sidebar.logic.test.ts` (128 passed, 4 new: acknowledgeme
407415
upstream workflow arriving in a rebase is opt-**in**: it ships only if it passes the
408416
standing rule. Separately, `desktop-artifacts.yml` is fork-owned and can drift against
409417
upstream's desktop build requirements **without ever showing up as a merge conflict** —
410-
diff it against upstream's `release.yml` build job on every sync.
418+
diff it against upstream's `release.yml` build job on every sync. Whatever that re-derive
419+
produces, **keep the dev-release tail**: every run publishes a `desktop-dev-<run number>`
420+
prerelease (a development build) and prunes older `desktop-dev-*` releases down to the
421+
current one plus two. That publish-and-prune goal is fork intent, not an implementation
422+
detail — re-apply it even if the build job around it is rebuilt from scratch.
411423
- **Redundancy check (as of `c3e37094`; re-checked at `2c4158f8` — upstream added no workflow and touched nothing under `.github/` in that range, so the set replayed untouched): keep, and at the 2026-08-21 rebase this entry is where the rebase
412424
spent its effort.** Upstream added **no** new workflow in the `db0659fe..c3e37094` range, so
413425
there was nothing new to accept or decline — the first rebase in a while with no opt-in

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
> On top of upstream this fork carries a deliberately thin layer: a **workflow set a fork can
1111
> actually run** — standard GitHub-hosted runners instead of upstream's Blacksmith ones, no
1212
> workflow needing credentials a fork lacks, and unsigned desktop artifacts built on every
13-
> push to `main` — plus three server changes for **running more than one provider instance**
13+
> push to `main` and published as pruned development-build prereleases — plus three server
14+
> changes for **running more than one provider instance**
1415
> (a logged-out Claude instance reports as unauthenticated instead of ready, usage scans every
1516
> configured instance, and the usage report breaks totals down per instance), a **configurable
1617
> worktree branch prefix**, and four web UX changes (the sidebar new-thread button under the

‎docs/internals/ci.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ macOS runner is unavailable to it.
2828

2929
[`.github/workflows/desktop-artifacts.yml`](../../.github/workflows/desktop-artifacts.yml) builds
3030
macOS (`arm64` and `x64`), Linux (`x64`), and Windows (`x64`) desktop artifacts on every push to
31-
`main`, plus on manual dispatch. Artifacts are **unsigned** and uploaded as workflow artifacts
32-
(14-day retention); nothing is published to a GitHub Release.
31+
`main`, plus on manual dispatch. Artifacts are **unsigned**, uploaded as workflow artifacts
32+
(14-day retention), and published as a GitHub **prerelease** tagged `desktop-dev-<run number>` —
33+
a development build, never marked latest. The workflow prunes older `desktop-dev-*` releases,
34+
keeping only the current one plus two.
3335

3436
[`.github/workflows/issue-labels.yml`](../../.github/workflows/issue-labels.yml) keeps the labels the
3537
issue forms apply (`bug`, `enhancement`, `needs-triage`) in sync.

‎docs/operations/release.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
> Cloudflare, PlanetScale, Axiom, Clerk, Apple, Azure, npm, and GitHub App credentials a
88
> fork does not have. Kept as upstream reference for rebases. This fork builds unsigned
99
> desktop binaries from `.github/workflows/desktop-artifacts.yml` on every push to `main`
10-
> and does not cut releases; see [CI quality gates](../internals/ci.md).
10+
> and publishes them as pruned `desktop-dev-*` prereleases (development builds, keeping the
11+
> current one plus two); it cuts no stable releases. See
12+
> [CI quality gates](../internals/ci.md).
1113
1214
This document covers the unified release workflow for stable and nightly desktop releases.
1315

0 commit comments

Comments
 (0)