Skip to content

fix(desktop): stop offering an update track the app cannot switch to - #442

Merged
rynfar merged 2 commits into
pylonfrom
t3code/fix-nightly-macos-update
Sep 10, 2026
Merged

rynfar merged 2 commits into
pylonfrom
t3code/fix-nightly-macos-update

Conversation

@rynfar

@rynfar rynfar commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

The problem

Settings → About offered an Update track select — "Use stable releases or nightly builds. Switch back anytime." Picking Nightly on a stable install offers a download that can never install, then hangs the app on restart. No nightly app ever appears in /Applications.

Stable and nightly stopped being interchangeable in 7a61251, which gave them separate identities on purpose: com.pylon.code vs com.pylon.code.nightly, Pylon (Alpha) vs Pylon (Nightly), ~/.pylon-code vs ~/.pylon-code-nightly. macOS in-place updates go through Squirrel.Mac, which validates the downloaded bundle against the running app's designated requirement — and that pins the bundle identifier. From two installed builds:

$ codesign -d --requirements - "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/Applications/Pylon (Alpha).app"
designated => identifier "com.pylon.code" and anchor apple generic and ... subject.OU = ZCL6PL2MJJ

$ codesign -d --requirements - "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/Applications/Pylon (Nightly).app"
designated => identifier "com.pylon.code.nightly" and anchor apple generic and ... subject.OU = ZCL6PL2MJJ

The nightly bundle cannot satisfy the stable app's requirement, so the install is refused. MacUpdater.quitAndInstall() then registers a listener for a native update-downloaded event that never arrives — by which point installDownloadedUpdate has already stopped every backend and set quitting, which is the hang. And Squirrel only ever replaces the running bundle, so a second app was never on the table. Windows and Linux fail differently for the same reason, and even a hypothetical success would relaunch against a different runtime home, so the user's projects would look erased.

Anyone who already flipped the select is stuck: their stable install has updateChannel: "nightly" on disk and keeps polling a feed it cannot install from.

The fix

The track becomes a property of the build rather than a preference.

  • DesktopUpdates derives its channel from the app version. setChannel is gone from the service, the IPC method, the preload bridge, and the contract.
  • updateChannel / updateChannelConfiguredByUser leave DesktopSettings. Existing documents still decode — excess keys are ignored — and shed the keys on the next write, which is what unsticks installs currently pointed at the wrong feed.
  • Remote SSH environments pick their t3 dist-tag from the build for the same reason, instead of from the setting.
  • The settings row keeps its place and tells the truth: which build you are on, that the other installs alongside with its own projects and settings, and a button that opens its download page.

Net −300 lines.

Verification

  • vp test run over apps/desktop/src/updates/, apps/desktop/src/settings/, apps/desktop/src/window/, apps/web/src/components/settings/, packages/ssh/src/command.test.ts — 44 files, 421 tests, all passing.
  • typecheck clean for @t3tools/desktop, @t3tools/web, @t3tools/contracts. (Note for reviewers: this repo's typecheck exits 0 with errors present — read the output.)
  • vp lint clean on every changed file, apart from a pre-existing unused useNavigate import in SettingsPanels.tsx that is also on pylon.
  • New regression tests: DesktopUpdates follows the stable feed on a stable build and the nightly feed on a nightly build; DesktopSettings ignores a retired update-channel preference and drops it on write.

Not verified in a running client — no UI screenshots. The changed row is text plus a button and I did not spin up a browser; happy to do a pass if you want one before merge.

Out of scope, but blocking for users

pylon-code.com is serving a September 2 build (last-modified: Wed, 02 Sep 2026 on both / and /download/), so the nightly download toggle from #385 is not live and ?channel=nightly currently lands on the stable page. Git deploys are enabled in apps/marketing/vercel.ts, so this needs a look in the Vercel dashboard. Until it redeploys, the new Get nightly button — and the link in docs/user/updating.md — points at a page without the nightly option.

Model: Claude Opus 5 via Claude Code.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Settings offered an "Update track" select that promised stable and nightly
builds were interchangeable. They have not been since 7a61251 split their
identities: nightly declares com.pylon.code.nightly, is named Pylon (Nightly),
and keeps its data in ~/.pylon-code-nightly. Picking Nightly on a stable install
pointed electron-updater at the nightly feed and offered a download that can
never install. Squirrel.Mac validates an update against the running app's
designated requirement, which pins the bundle identifier:

  Pylon (Alpha):   identifier "com.pylon.code" and anchor apple generic ...
  Pylon (Nightly): identifier "com.pylon.code.nightly" and anchor apple ...

So the install is refused, and MacUpdater.quitAndInstall then waits on a native
update-downloaded event that never arrives — with the backends already stopped,
which reads as the app hanging on restart. No second app appears in
/Applications either, because Squirrel only ever replaces the running bundle.
Windows and Linux fail differently for the same reason. Even a hypothetical
success would relaunch against a different runtime home, so the user's projects
would look erased.

The track is now a property of the build, not a preference: DesktopUpdates
derives it from the app version, and setChannel is gone from the service, the
IPC method, the preload bridge, and the contract. updateChannel and
updateChannelConfiguredByUser leave DesktopSettings; existing documents still
decode (excess keys are ignored) and shed the keys on the next write, which is
what unsticks anyone whose stable install is currently polling the nightly feed.
Remote SSH environments pick their t3 dist-tag from the build for the same
reason.

The settings row keeps its place and tells the truth instead: which build you
are on, that the other one installs alongside with its own data, and a button
that opens its download page.

Model: Claude Opus 5 via Claude Code.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.7 KiB 13.8 KiB +80 B (+0.6%) 15.1 KiB ✅
Codex Thread snapshot wire 7.1 KiB 7.1 KiB −11 B (−0.2%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.6 KiB 6.7 KiB +91 B (+1.3%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 57.3 KiB 57.4 KiB +88 B (+0.2%) 66.4 KiB ✅
Codex Live turn messages 8 10 +2 (+25.0%) 21 ✅
Claude Total thread wire 13.8 KiB 13.8 KiB +17 B (+0.1%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB +5 B (+0.1%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.7 KiB 6.7 KiB +12 B (+0.2%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 58.2 KiB 58.2 KiB 0 B (0.0%) 66.4 KiB ✅
Claude Live turn messages 10 10 0 (0.0%) 21 ✅

Baseline: ab49d53 · PR result: 23cfceb · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 111.2 KiB
  • Claude decoded thread snapshot: 111.9 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@rynfar
rynfar merged commit 0664ace into pylon Sep 10, 2026
17 checks passed
@rynfar
rynfar deleted the t3code/fix-nightly-macos-update branch September 10, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant