Make updates download themselves and land in a banner (fixes #173) - #183
Closed
nthomsencph wants to merge 10 commits into
Closed
nthomsencph wants to merge 10 commits into
nthomsencph wants to merge 10 commits into
Conversation
Decisions taken, the Claude Code facts the plan relies on, target architecture, spikes to run first, a phase 1 file-by-file breakdown (version floor, worktree relocation to .claude/worktrees with a migration dialog, SupervisorService, attach-based PTYs, hook port file, activity reconciliation, foreign sessions), a phase 2 sketch and risks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2daUsvEQ2nJ6oftunoWpd
Ran S1-S7 against Claude Code 2.1.278: --bg starts without a prompt and the job id is the session UUID prefix; attach works in node-pty and left-arrow opens agent view; the dispatch env is frozen into the job and reused on respawn; the agent_needs_input/agent_completed matchers never fire in the session's own hooks; listing costs 0.3 s and fs.watch on ~/.claude/jobs works as a trigger; a Dash linked worktree is not re-isolated. Migration needs a claude rm before resuming into a moved worktree, resumes must re-pass --name, and transcripts stay under the old path. Hook port moves to a file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2daUsvEQ2nJ6oftunoWpd
…ude/worktrees PR 1 of the supervisor-sessions plan (docs/specs/2026-09-20-claude-code- supervisor-sessions.md §6.1 and §6.2). Version floor - MIN_CLAUDE_VERSION (2.1.257) with pure parse/compare helpers in claudeCli.ts. - The startup claude --version probe is memoised and awaitable; pty:startDirect awaits it and refuses with a typed UNSUPPORTED_CLI error below the floor. - app:detectClaude returns ClaudeCliInfo (minVersion, supported, reason) and can re-run the probe. MainContent renders ClaudeCliGate instead of the task terminal on a missing or too-old CLI; the settings card shows the same state. - PostCompact/StopFailure hook gates removed: both predate the floor. Worktree relocation - WorktreeService.getWorktreesDir is now <repo>/.claude/worktrees, the layout Claude Code assumes; ensureWorktreesDir also appends .claude/worktrees/ to .git/info/exclude so the main checkout stays clean. The reserve pool and the orphan-reserve sweep follow (the sweep also clears the legacy dir). Migration - tasks.previous_path records a moved task's old path. findLatestSessionId and token aggregation search both transcript dirs, since Claude keeps writing a moved session under the original cwd's encoded dir. - WorktreeMigrationService plans and executes git worktree move per project (kills the task's PTYs first, lifts a leftover worktree lock, reports per-task failures). Offered once per launch by WorktreeMigrationModal with Later / Move now / Don't ask again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2daUsvEQ2nJ6oftunoWpd
State of the branch after PR 1, what changed and where, container quirks, the manual verification pass PR 1 still needs, and a build order for PR 2 with the spike-verified facts it depends on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2daUsvEQ2nJ6oftunoWpd
Task sessions are now dispatched with `claude --bg` in the task worktree and shown through `claude attach`, so they outlive task switches, renderer reloads and Dash quitting. Hooks read the HookServer port from a file under userData instead of the (supervisor-frozen) environment. The supervisor listing is reconciled into the activity monitor and drives a new `stopped` state plus a per-project "Other sessions" group with attach, stop, remove and adopt-as-task. Archive stops the session, delete removes it, restart re-dispatches with --resume. New setting: stop sessions on quit. Also: DASH_USER_DATA_DIR / DASH_DEV_URL overrides for running a checkout beside the installed app, README/CLAUDE.md updates, and a fix to the migration test on macOS (/private/tmp). Claude goes brr.. via Dash
Each project in the "Move task worktrees" dialog gets a checkbox (all on by default). Unticked projects stay at the old location and are offered again next launch, so a first move can be tried on one project. Claude goes brr.. via Dash
A task row whose worktree directory exists at neither the old nor the new location has nothing to move; it used to fail with "Worktree directory is missing" and be offered again on every launch. Claude goes brr.. via Dash
electron-builder up to 26.16.0 unlocked its temporary signing keychain with the certificate password, which the current macos-latest runner rejects (the v0.15.1 release run failed there). 26.16.1 fixes it. docs/specs/2026-09-21-main-handoff-release-0.16.md describes what must happen on main before v0.16.0 is pushed: prove signing on a branch, harden the auto-updater (auto-download, install on quit, persistent banner), and verify the 0.15.0 → 0.16.0 → 0.16.1 update chain. Claude goes brr.. via Dash
workflow_dispatch runs this workflow on whatever branch it is triggered from, and the release job would happily publish v<package.json version> from there. Guard it on refs/heads/main so signing can be proven on a branch without cutting a release. Pin CI to the toolchain the repo actually uses: Node 24 (.nvmrc) and pnpm 10 (the lockfile is v9.0 and local pnpm is 10.33), so a lockfile format bump can't break a release. Upload latest.yml from the Windows job too, so a future Windows updater has a manifest. Narrow the manifest globs from release/*.yml to release/latest*.yml while doing so: *.yml also matches builder-debug.yml, which is why v0.15.0 shipped one as a release asset, and with three jobs uploading it merge-multiple would collide on it. Claude goes brr.. via Dash Claude-Session: https://claude.ai/code/session_017gtmbWYyAsYEBiYQDnf4vy
Issue #173: "Your update available toast is toast. It keeps spamming me, and when i press update it breaks." Both halves were real. The spam: the toast was raised with duration Infinity and no sonner id, so every event stacked another one that never dismissed; and checkForUpdates only short-circuited on `downloading`/`ready`, never on `available`. Once an update was found and ignored, each background check (launch + every 4h) ran a real check, electron-updater re-emitted update-available, and another permanent toast landed on the pile. The break: the toast's Download action called autoUpdateDownload() and threw the IpcResponse away, while downloadUpdate() rejected with "No update available to download" whenever state !== 'available' — exactly the case when clicking any stacked toast after the first, or during an in-flight check. The click did nothing at all, silently. Nothing consumed autoUpdate:downloadProgress either, so even a working download showed no sign of life for ~130MB. Rather than patch the toast, remove the need for one. Updates now download on their own and install on quit, so there is no Download button to mis-fire; a persistent banner at the foot of the sidebar carries progress and the Restart action, and stays until acted on. Under 0.16.0 a restart is cheap — task sessions live under the supervisor and re-attach afterwards — so the copy says so. The five granular autoUpdate:* channels collapse into one autoUpdate:status carrying the whole state, which the renderer keeps in runtimeStore instead of stitching together locally in two places. The event→state mapping and the "is a check worth running" policy move into autoUpdateState.ts, pure and unit-tested, following supervisorSession.ts. Also: check on window focus and powerMonitor resume (cooldown-bounded), keep the last error in the status so a failure is visible in Settings rather than silent, log updater events to <userData>/logs/updater.log, and show release notes (#73). Settings → Updates gains last-check time, the error text and a release-notes link. Claude goes brr.. via Dash Claude-Session: https://claude.ai/code/session_017gtmbWYyAsYEBiYQDnf4vy
Collaborator
Author
|
Closing — landing this directly on main instead. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #173. Addresses #73.
What was wrong
Both halves of the report — "It keeps spamming me, and when i press update it breaks" — were real, and both are still in
maintoday.Toast.tsxandAutoUpdateService.tswere last touched 2026-06-23, the day before the issue was filed.The spam. The toast was raised with
duration: Infinityand no sonnerid, so every event stacked another one that never dismissed. AndcheckForUpdatesshort-circuited only ondownloading/ready, never onavailable— so once an update was found and ignored, every background check (launch, then every 4h) ran a real check, electron-updater re-emittedupdate-available, and another permanent toast joined the pile.The break. The toast's Download action called
autoUpdateDownload()and discarded theIpcResponse, whiledownloadUpdate()rejected with'No update available to download'wheneverstate !== 'available'— exactly the case when clicking any stacked toast after the first, or during an in-flight check. The click did nothing at all, with no error and no progress. Nothing consumedautoUpdate:downloadProgresseither, so even a working download was silent for ~130 MB.Approach
Rather than patch the toast, remove the need for one. Updates download on their own and install on quit, so there is no Download button left to mis-fire. A persistent banner at the foot of the sidebar carries progress and the Restart action and stays until acted on.
autoDownload = true,autoInstallOnAppQuit = true. A restart is cheap under 0.16.0 — task sessions live under the supervisor and re-attach — and the copy says so.autoUpdate:*channels collapse into oneautoUpdate:statuscarrying the whole state. The renderer keeps it inruntimeStoreinstead of stitching it together locally in two places.autoUpdateState.ts— pure and unit-tested, following thesupervisorSession.tsprecedent.powerMonitorresume, both cooldown-bounded.<userData>/logs/updater.log.ProgressBarprimitive incomponents/ui/.UsageBarcolours by threshold (amber, then red), which would paint a nearly-finished download red.Testing
autoUpdateState.test.ts(14 cases) pins the transitions, including the two that caused #173: a re-check is refused once an update is in hand, and a latenot-availablecannot wipe an in-flight download.updateStatus.test.tspins the wording — notably that a failed check no longer reads as "You're up to date".runtimeStore.test.tsgains 5 cases for the subscription, the post-check reconcile and error surfacing.Full suite green (1009 passed), type-check and lint clean.
Not verified end to end. The updater does not initialise in
--dev, and macOS notarization is currently failing with a 401, so nothing can be published to test the real download/install path. This needs theAPPLE_APP_SPECIFIC_PASSWORDsecret rotated first — see the release-pipeline branch. Worth a real 0.16.0 → 0.16.1 hop before trusting it.Note on versioning
Bumps to 0.16.1 so 0.16.0 stays shippable as-is, matching the handoff's two-hop verification plan: ship 0.16.0, confirm the old updater still carries users across, then land this.
🤖 Generated with Claude Code
https://claude.ai/code/session_017gtmbWYyAsYEBiYQDnf4vy