A self-host operator running a fork of Multica with their own patches would
have their daemon silently upgraded to the upstream GitHub release, clobbering
the fork. Self-host setups also routinely pin to an older server, so a fresh
CLI may no longer talk to it.
Flip the default: auto-update remains opt-in on api.multica.ai and defaults to
off on any other server URL. Either side can override via
MULTICA_DAEMON_AUTO_UPDATE.
Co-authored-by: multica-agent <github@multica.ai>
* feat(daemon): auto-update CLI when idle (MUL-2100)
Add a periodic poller that checks GitHub for a newer multica release
every hour and self-updates when the daemon is idle, reusing the same
brew-or-download upgrade path the Runtimes-page "Update" button already
runs.
- Refactor handleUpdate to call a shared runUpdate(target) helper so
both server-triggered and auto-triggered upgrades go through the same
brew detection + atomic replace + restart.
- New autoUpdateLoop gates each tick on: opt-out flag, Desktop launch
source, dev-build version, an in-flight update, and active tasks. The
idle gate guarantees we never interrupt a running agent — busy ticks
silently retry at the next interval.
- Config: MULTICA_DAEMON_AUTO_UPDATE=false to disable (also via
--no-auto-update), MULTICA_DAEMON_AUTO_UPDATE_INTERVAL to retune the
poll period.
- IsNewerVersion / IsReleaseVersion helpers in the cli package, with
tests covering patch/minor/major bumps, dev-describe strings, and
malformed input.
- Daemon-side tests cover every skip path (updating, active tasks,
fetch failure, no-newer) plus the success path that fires
triggerRestart while keeping the updating flag held to the end.
Co-authored-by: multica-agent <github@multica.ai>
* fix(daemon): close idle race + verify checksum in auto-update (MUL-2100)
Two issues raised in PR #2679 review:
1. The first idle check in tryAutoUpdate only ran before the release-metadata
fetch, so a poller that won the claim race during the fetch could end up
handing handleTask a task that triggerRestart was about to cancel via root-
ctx cancellation. Add a strict claim barrier: runRuntimePoller now
tryEnterClaim()s before ClaimTask, and tryAutoUpdate flips pauseClaims
under claimMu only after observing claimsInFlight + activeTasks == 0.
Pollers that were already mid-claim hold claimsInFlight > 0, so the barrier
refuses to engage and the update defers to the next tick.
2. The direct-download path replaced the running binary with whatever bytes
GitHub returned, without checking checksums.txt. Pull the manifest first,
buffer the archive, and reject on SHA-256 mismatch before extraction. The
GoReleaser config already publishes checksums.txt; we just consume it.
Also tighten parseReleaseVersion so it stops accepting dev-describe shapes
like "v0.1.13-5-gabcdef0" through the patch trim, matching its docstring.
The auto-update loop already guards on IsReleaseVersion, but the lenient
parser was a footgun and the existing test name even said "not newer" while
asserting the opposite.
Tests:
- TestTryAutoUpdate_DefersWhenClaimInFlightAtBarrier (new race coverage)
- TestTryAutoUpdate_HoldsBarrierAcrossRestart / ReleasesBarrierOnUpgradeFailure
- TestTryEnterClaim_RespectsBarrier
- TestFindChecksumManifestAsset / TestParseChecksumManifest / TestVerifyAssetSHA256
- TestIsNewerVersion: dev-describe cases now expect false (matches docstring)
Co-authored-by: multica-agent <github@multica.ai>
* chore(daemon): default auto-update poll interval to 6h (MUL-2100)
1h was overly chatty for a release that lands at most a few times a week.
Operators who want a different cadence can still set
MULTICA_DAEMON_AUTO_UPDATE_INTERVAL or --auto-update-interval.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>