Repository navigation
Runtime update rolls back the binary after the new daemon already migrated the global DB (20 s restart watchdog vs ~80 s boot), leaving an unbootable install #559
Description
Summary
compozy update (runtime target, install_method: direct-binary) swapped the binary from 0.3.0-beta.22 to 0.3.0-beta.23 and restarted the daemon. The new daemon migrated the global database (goose stream global 99 → 107) within its first second, then took ~80 s to become ready. The restart watchdog only waits ~20 s, declared the replacement daemon failed, and the updater rolled the binary back to beta.22 — but the database migration is irreversible, so the restored beta.22 can no longer open ~/.compozy/compozy.db:
error: daemon: open global database "~/.compozy/compozy.db": store: initialize sqlite database "~/.compozy/compozy.db": globaldb: inspect operator home migration status: store: database schema is ahead of this binary: ~/.compozy/compozy.db stream global is at version 107, binary head is 99; install a newer CompozyOS binary or stop
Net effect: a slow boot was turned into a permanently unbootable install (CLI daemon and desktop app both dead) that required manually installing the runtime tarball and the desktop app bundle to recover. The same "update leaves the home unbootable" outcome was previously reported in #451 (different trigger). The structural defect here is that the rollback path runs after an irreversible schema migration has already been applied.
Environment
- OS: macOS (Darwin 25.6.0), Apple Silicon
- Runtime:
0.3.0-beta.22→0.3.0-beta.23, direct binary at~/.local/bin/compozy, updated viacompozy update - Desktop app:
/Applications/CompozyOS.app0.3.0-beta.22, managed runtime copy at~/.compozy/bin/compozy(.desktop-provenance.json→installed_by: desktop-app) - Home size (relevant, this is what makes boot slow):
~/.compozy/compozy.db145 MB;~/.compozy/sessions262 session dirs, 836 MB checksums.txtof the release matched both downloads (compozy_darwin_arm64.tar.gz03f83fa5…,CompozyOS-0.3.0-beta.23-mac-arm64.zip22f7bcc0…), so this is not a corrupt asset
Steps to reproduce
The only precondition is a home directory where the new daemon needs more than ~20 s to reach readiness. A large home does that naturally (see numbers above). If you don't have one, a deterministic way to simulate it (suggested, I have not run this variant myself) is to kill -STOP the replacement daemon right after it logs store.migrations.applied stream=global, wait 30 s, then kill -CONT it.
- Have the daemon running on beta.22 (
compozy status→running). - Run
compozy update(both targets pending: runtime beta.23 + app beta.23). - Watch
~/.compozy/logs/compozy.logand~/.compozy/restarts/<id>.json.
Actual result (timeline, UTC, taken from compozy.log, restarts/*.json, update-history.jsonl)
| time | event |
|---|---|
| 21:33:32 | update operation 048428219f… started, targets [runtime, app], active_target: runtime |
| 21:33:50.24 | restart request f02bf261… created (old_pid 83857) |
| 21:33:50.27 | old beta.22 daemon: daemon: received shutdown signal signal=terminated → clean shutdown |
| 21:33:51 | new beta.23 daemon: store.migrations.applied stream=global version=107 applied_count=107 duration_ms=1023 ← DB is now beta.23-only |
| 21:33:54 | daemon: Loop boot reconciliation complete … duration_ms=3296; store.migrations.applied stream=memory version=3; clientstate opened |
| 21:33:54 → 21:34:15 | no log output at all for ~20 s |
| 21:34:10.93 | restart request marked failed: daemon: replacement daemon did not become ready before timeout (20.7 s after it was created) |
| 21:34:15 → 21:34:17 | store.migrations.applied stream=session version=8 for dozens of session DBs |
| 21:34:35.34 | update operation: phase: rolled-back, last_error: "cli: daemon restart did not complete before timeout: context deadline exceeded"; binary restored from backup_path (beta.22) |
| 21:34:59.99 | new daemon: network.started |
| 21:35:00 | extensions loaded (forge-github, spec-cycle, tailscale) |
| 21:35:10.41 | new daemon: daemon: boot reconciliation complete, scheduler.started ← it did become ready, ~80 s after launch |
| 21:35:10.42 | scheduler.shutdown … [memory] runtime snapshot phase=shutdown uptime_seconds=80 ← the now-ready beta.23 daemon is terminated |
| 21:35:33 → 21:35:40 | desktop app bootstrap tries to start the (restored) beta.22 runtime; every attempt exits immediately with the database schema is ahead of this binary … version 107, binary head is 99 error above |
~/.compozy/restarts/f02bf261-….json after the fact:
{
"operation_id": "f02bf261-48f1-44da-9717-62292ea3c36d",
"status": "failed",
"old_pid": 83857,
"old_started_at": "2026-09-08T04:16:40Z",
"active_session_count": 0,
"failure_reason": "daemon: replacement daemon did not become ready before timeout",
"started_at": "2026-09-08T21:33:50.237675Z",
"completed_at": "2026-09-08T21:34:10.9292Z"
}compozy update -o json afterwards (runtime block):
"runtime": {
"status": "failed",
"install_method": "direct-binary",
"current_version": "0.3.0-beta.22",
"latest_version": "v0.3.0-beta.23",
"restored_version": "0.3.0-beta.22",
"daemon_restarted": false,
"message": "The runtime update failed and the previous version was restored.",
"last_error": "cli: daemon restart did not complete before timeout: context deadline exceeded"
}State after the update "completed": no daemon process running, compozy daemon start fails instantly with the schema-ahead error, desktop app stuck at state: error, code: runtime_start_failed with the same message. Nothing in the user-facing output mentions that the DB was migrated or that the restored binary cannot open it.
Expected result
Any one of these would have avoided the outage:
- Never roll back the binary after an irreversible migration has run. If the new daemon has already applied schema migrations, rollback must either restore a pre-migration DB backup taken by the updater, or be skipped with a clear message ("new daemon migrated the DB; keeping new binary, retry
compozy daemon start"). - Order boot so migrations happen after readiness gating, or run them as a separate, explicit pre-flight step whose duration is not counted against the restart watchdog.
- Make the restart watchdog progress-aware (extend while the daemon keeps logging progress) or at least much longer than 20 s. 20 s is below the boot time of a moderately used home; the daemon here was 60 s away from being healthy when it was declared dead.
Related defects observed while recovering (same root pattern: fixed short timeouts + kill-on-timeout)
compozy daemon start(CLI) returnederror: cli: daemon did not become ready before timeout: context deadline exceededwhile the detached daemon kept booting and answeredcompozy status~35 s later. The exit code says "failed" for a start that succeeded.- Desktop app kills a foreign-version daemon. With a healthy beta.23 daemon running (started from
~/.local/bin), launching the beta.22 app went throughphase: resolvefor 28 s, then sent SIGTERM to that daemon (daemon: received shutdown signal signal=terminatedat 21:44:52) and started its own bundled beta.22 runtime ("resolution":"start"), which of course died on the schema-ahead error. There is no attach-to-newer-runtime path, so the running daemon was the only working thing and the app removed it. - Staged app update never applies once the app can't boot.
compozy updatereportedCompozyOS app update staged; applies on next launch, but the app never reached the point of applying it (it errors inruntime_start_failedfirst).~/Library/Caches/@compozydesktop-updater/only contained the previous beta.22 zip, nothing for beta.23. So the app-side update cannot be the recovery path either. - Desktop bootstrap: 15 s per attempt × 5, and it reaps the daemon it started. After manually installing app beta.23,
desktop-bootstrap.jsonlshowsresolution: provision→Starting the CompozyOS runtime→ 15 s laterThe runtime did not become ready; retrying, 5 times, ending incli: reap unready detached daemon: signal: killed. Each retry kills a daemon that was mid-boot and starts over, so it can never succeed on a home where boot > 15 s. The only thing that worked was starting the daemon from the CLI first, waiting forcompozy status, then launching the app →Attached to the running CompozyOS runtime. - Stale
failedstatus. After the runtime was current again,compozy update --checkkept reportingruntime.status: failed/ "The runtime update failed and the previous version was restored." because the lastruntimeblock in~/.compozy/logs/update-history.jsonlhasphase: rolled-back. It only cleared after deleting that line.
Impact
Complete outage of the local CompozyOS install (CLI daemon and desktop app) after a routine compozy update, with no automatic recovery path and an error message that only makes sense if you already know the updater rolled back the binary. This is the second time on this machine that a runtime update's rollback left the DB ahead of the binary (beta.21→beta.22 on 2026-09-01 had a different crash cause but the same rollback-after-migration outcome), and #451 describes a third unbootable-after-update case.
Workaround that recovered the install (manual, in this order)
gh release download v0.3.0-beta.23 --repo compozy/compozy -p compozy_darwin_arm64.tar.gz -p checksums.txt; verify sha256;install -m 0755 compozy ~/.local/bin/compozy.compozy daemon start(ignore its timeout error), wait untilcompozy statusanswers.- Do not launch the beta.22 desktop app at this point (it will kill the daemon). Download
CompozyOS-0.3.0-beta.23-mac-arm64.zip, verify sha256,ditto -x -k,codesign --verify --deep --strict, replace/Applications/CompozyOS.app. - Launch the app only while the daemon is already ready → it attaches (
"resolution":"attach") and provisions~/.compozy/bin/compozywith the beta.23 copy. compozy update --cancelto clear the dormantwaiting-for-appoperation; remove thephase: rolled-backline fromupdate-history.jsonlto clear the stalefailedstatus.
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsDone
Activity