Skip to content

ci(release): add a tested rolling nightly workflow and a shared macOS build - #1012

Merged
Juliusolsson05 merged 10 commits into
mainfrom
feat/nightly-release
Sep 19, 2026
Merged

Juliusolsson05 merged 10 commits into
mainfrom
feat/nightly-release

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Problem

Releases need a manual workflow_dispatch with a version-matching tag, and every asset name carries the version. So users sit on stale builds, and no URL for "the current build" survives a bump (#1011).

Implemented

  • .github/workflows/nightly.yml, scheduled 05:00 UTC plus dispatch with a force input:
    1. validate the signing secrets;
    2. decide: skip only if the triggering SHA already has a complete nightly;
    3. build through the shared pipeline;
    4. rename to fixed names (Agent.Code-nightly-<arch>.dmg/.zip) and drop the updater metadata;
    5. publish the rolling nightly prerelease with a marker-less body;
    6. only after every upload succeeds, record the built-from: <sha> marker.
  • scripts/release/nightly.mjs (decide, rename, notes; Node built-ins only). It is the single implementation of the workflow's logic, so it is testable.
  • .github/workflows/reusable-app-build-macos.yml: release.yml's build and package jobs, extracted verbatim (three documented deltas). release.yml gains only a comment pointing at the mirror.
  • .gitignore: release anchored to /release/. The unanchored rule silently ignored scripts/release/ and testing/system/release/, which would have shipped the workflow without its script.

Review (2026-09-19, two independent reviewers, CHANGES REQUESTED) → resolved

Finding Resolution
Marker written before the uploads, so a failed upload is skipped forever (major) Publish without the marker; gh release edit writes it last. A skip also requires all 4 assets uploaded.
CRLF body → \r kept → never skips → git log crash (major) CRLF-safe, exact-40-hex parse. Reproduced with a real CRLF body: the old sed parse returned fd5a2a1f…$'\r' ≠ HEAD.
Unreachable previous SHA crashes git log after a full build (major) git cat-file -e guard, falling back to recent history.
Pre-merge branch dispatch is impossible (process) Plan corrected: workflow_dispatch needs the file on the default branch, so verification runs after merge.
"Queues, never cancels" is false; could cancel a pending release.yml (minor) Own group, release-nightly.
"Stable outranks by date" is false; softprops sends make_latest on every update (minor) The nightly is a prerelease with make_latest: false. The stable release owns "latest".
Tag lands on the default-branch HEAD, not the built commit (nit) target_commitish: head-sha. The body links the exact tree and states that the tag and "Source code" archives are frozen at the first nightly.
nullglob, any API error treated as a 404, unused env (nits) Fixed. Only a 404 means "no nightly"; a real 401 fails loudly.
Delete→upload 404 window (minor) Documented as an accepted trade-off. A fresh page falls back to Releases.
Drift warning in one direction only (minor) Comment pointer added in release.yml.

Verification

  • testing/system/release/nightly.test.ts: 16 system tests, written before the script, all failing first, now passing.
    • They run the real script as a process.
    • A stub gh replays recorded payloads (testing/fixtures/release-nightly, provenance in its README): our v0.0.2-beta.1 release, the real 404 for nightly, a real 401, and a real CRLF release body.
    • Notes run against a real git repo, and rename runs on the real published artifact names.
  • actionlint is clean on all three workflow files.
  • End-to-end after merge: gh workflow run nightly.yml publishes the first nightly prerelease, and a second dispatch with no new commits must skip. The run link will be added here.

Follow-ups

  • Adopt the reusable jobs in release.yml once a nightly has run green.
  • Stamp the version on nightlies.
  • Landing-page status line for stable vs. nightly (release-readiness Stage 8).

Refs #1011

🤖 Generated with Claude Code

Juliusolsson05 and others added 8 commits September 18, 2026 19:28
…- Refs #1011

nightly.yml schedules a 05:00 UTC run that skips when main has no
commits past the previous nightly's built-from marker, builds through
the same proven pipeline (extracted verbatim into
reusable-app-build-macos.yml), and republishes the rolling nightly
release with fixed asset names so external links never change. The
nightly joins the release-macos concurrency group so it can never
package or publish alongside a versioned release.

release.yml is deliberately untouched: switching it to the shared jobs
is a follow-up after the first green nightly, so a new-trigger
regression cannot entangle with the known-good versioned path.
Refresh the base so CI measures the merge result against current main
(54 commits behind; no conflicts, no workflow or build/ drift).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The unanchored `release` pattern matched every directory named release
at any depth, so scripts/release/ and testing/system/release/ were
silently ignored. The nightly workflow would have shipped without the
script it runs. Only electron-builder's root release/ output is meant.

Refs #1011

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…orded GitHub payloads

Written before the script. All 16 cases failed first. The fixtures are
real captures:
- the v0.0.2-beta.1 release payload;
- the 404 gh returns for the not-yet-existing nightly tag;
- a real 401;
- a react release body stored with CRLF, as the web UI stores bodies.
The derived complete-nightly case is a visible transformation of the
recorded payload. The notes cases run against a real git repo, and the
rename cases use the real published artifact names.

Refs #1011

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… review defects

The PR review found three real defects in the inline workflow bash. A
CRLF body kept a trailing \r, so the marker never matched. An
unreachable previous SHA crashed git log after a full build. The
built-from marker was written before the uploads, so a failed upload
was skipped forever. scripts/release/nightly.mjs is now the single
implementation. The marker is written last, after every asset uploaded;
a skip also requires all four assets uploaded; and only a 404 means no
nightly yet.

Also from review:
- the nightly is a prerelease with make_latest false, so the stable
  release owns latest (softprops sends make_latest on every update);
- it gets its own concurrency group, so it can never cancel a pending
  release.yml run;
- target_commitish is set, and a force dispatch input is added;
- release.yml gains a comment-only pointer to its reusable mirror.

Refs #1011

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Refs #1011

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 changed the title ci(release): add the rolling nightly workflow and shared macOS build ci(release): add a tested rolling nightly workflow and a shared macOS build Sep 19, 2026
Juliusolsson05 and others added 2 commits September 18, 2026 23:39
From the verification review of #1012:
- The commit list is capped at 200 lines with a compare link. GitHub
  rejects bodies over 125,000 characters, and a few weeks of failed
  nightlies would overflow and fail the final marker edit after a full
  build.
- The main-module guard compares realpaths. Invoked through a symlink,
  the script used to do nothing and exit 0, which looks exactly like a
  green skip.
- The body no longer points at a 'latest stable release' that cannot
  exist until release.yml can publish a non-prerelease.

New tests cover the cap (it fails first) and the exact-40-hex marker rule,
and the missing-architecture assertion is tightened. The last two were
coverage gaps; the behaviour already held.

Refs #1011

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Refs #1011

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Juliusolsson05
Juliusolsson05 merged commit 1195487 into main Sep 19, 2026
2 checks passed
@Juliusolsson05
Juliusolsson05 deleted the feat/nightly-release branch September 19, 2026 07:11
Juliusolsson05 added a commit that referenced this pull request Sep 19, 2026
…htly

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05 added a commit that referenced this pull request Sep 19, 2026
Pick up #1022 (goal-loop input freeze), #1023, #933 (tmux inventory) and
#1012. One conflict, in useKeybinds' onBlur: keep #1022's
dismissGoalLoop() and drop `pendingTiledResizeIndex`, which #992 deleted
along with tiled resize. #1022's overlay gate and rebind-safe dismiss
chord merged intact.

Verified: tsc -b clean. goal-loop + tile-tree + tldr renderer 175/175;
tmux + storage + remote-client unit 112/112. That includes #933's
real-workspace recovery test running against #992's model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant