feat(kimi-code): support automatic updates for native installations via staged swap - #2994
Conversation
…ia staged swap Native (SEA) installs previously could not self-update on Windows and relied on 'curl | bash' re-install on Unix. Replace both with a staged swap updater: - startup swaps in a staged binary (verified against the release manifest sha256, smoke-checked via --version) and re-execs it, so the running process never replaces itself (Windows-safe) - downloads run in a self-spawned hidden sub-command, in the background from the update preflight or in the foreground from 'kimi upgrade' - rollback from .bak on any swap failure; install failures keep the existing retry/prompt thresholds
🦋 Changeset detectedLatest commit: f576043 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 552e11954f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Real-binary smoke testing on macOS surfaced two cleanup gaps in the discard path: the claimed metadata file was unlinked after the staging dir rmdir (so the empty dir survived), and the staged exe was rediscovered via the already-claimed staged.json (so it leaked on the downgrade-guard path). Pass the known metadata through and order the unlink before the rmdir.
…ate leftovers at startup
…h window - The background native install no longer takes the outer install lock: the self-spawned downloader holds it for the whole download, and the parent's spawn-time lock raced the child into a false lastSuccess. - Smoke-check the staged exe before moving anything, so a bad staged binary is discarded with the install path never left empty; the remaining crash window is two adjacent atomic renames (documented, recoverable via the .bak or by re-running the install script).
…into feat/native-staged-auto-update
… order The restore-on-failure case now observes the early smoke check's --version spawn; only the re-exec spawn must be absent.
|
Addressed both Codex findings in P1 — executable path recoverability during swap: the P2 — lock contention with the spawned worker: Also merged |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 993dafa726
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The published per-release artifacts are the bare platform binaries (kimi-code-<target>[.exe]), not zip archives — the staging flow now streams the download straight to the staged exe after the manifest sha256 check, and the zip reader is dropped. Verified end-to-end on macOS against the live CDN: download -> sha256 match -> swap -> re-exec into the real released binary.
- re-exec: forward 128 + signo when the swapped-in child dies by signal instead of reporting exit 0 - __update_download: only exit 0 without staging when the lock holder is staging the SAME version; a different in-flight version (or a vanished lock) no longer surfaces as a successful foreground upgrade - staging: sweep orphaned .part downloads and unreferenced staged exes before downloading, preserving live swap claims and their payloads
|
Second review round addressed in Fixed (3):
Not changed (2):
Full suite green (2983 tests), typecheck + oxlint clean. |
The foreground 'kimi upgrade' path streamed 180 MB with a single static 'Downloading…' line. Render progress instead: a throttled in-place percentage line on a TTY, one line per 32 MB when piped, and plain MB counts when Content-Length is unknown.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 844f55871f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Windows real-machine verification of the background auto-update flow, end-to-end against the live CDN: fresh state → interactive TUI left open → background downloader spawned → ~143 MB staged → The first attempt, though, surfaced a robustness gap in the (pre-existing) install bookkeeping — not introduced by this PR, and it affects the package-manager flows too: An orphaned Observed sequence:
Two improvement ideas (fine as a follow-up, doesn't have to gate this PR):
Not a merge blocker — the failure mode needs an early parent exit and a dead downloader — but when it happens, a transient hiccup turns into 6 hours of silent no-update. |
Codex review: the manifest fetch cleared its timer once headers arrived, so a stalled response body hung the worker forever, and the binary download had no abort at all. The manifest timeout now covers body consumption, and the binary stream aborts after 30 s without a chunk (total duration stays unbounded for slow networks). The idle timeout is injectable for tests.
|
Third review round addressed in Fixed — timeout now covers body consumption ( The other four comments on the latest commit are re-posts of earlier rounds (already handled: smoke-before-rename + documented crash window; native path no longer holds the outer lock; lock-holder version check; staging orphan sweep). Full suite: 2989 tests green, typecheck + oxlint clean. |
…cord Windows real-machine verification surfaced that a parent exiting before the downloader's exit event leaves a fresh-looking 'active' record that silently blocks every background retry for the 6 h TTL. For native installs, lock liveness is the truth past a 60 s spawn grace window: a held lock means a download is running, a free lock means the record is an orphan and a new attempt may start. Package-manager sources keep the TTL behavior (no lock to prove liveness).
|
Thanks for the Windows real-machine run — great to have the background flow and the foreground The orphaned
Tests cover the three branches: orphan retried when the lock is free, no re-spawn while the lock is genuinely held, and no lock probe at all inside the grace window. Full suite green (2992 tests), typecheck + oxlint clean. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ef97ea38bf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
sweepStaleNativeUpdateArtifacts already detected an in-progress swap in a concurrent instance, but the result stayed inside the cleanup helper: startup still claimed a newly published staged.json and ran a second swap, so the two launchers could rename the install path and delete each other's rollback backup. Propagate the in-progress signal and skip claiming until the existing claim is released or goes stale.
1 similar comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11a913bf52
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Two related races around staged.json, both reported against the duplicate-downloader residual: - stageNativeUpdate deleted the previous record before downloading its replacement; a pathname-only delete can remove a concurrent worker's freshly published record, orphaning a payload whose worker already reported success. The old record now stays until the final atomic metadata write replaces it. - promoteStagedUpdateToManual wrote the marker unconditionally onto whichever generation owned staged.json. It now takes the adopted record and promotes only while the on-disk metadata still matches it, and the post-write confirmation requires the promoted candidate itself.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd9010095b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ring orphan cleanup Since the supersede path now keeps the previous staged.json until the final atomic write replaces it, an aged staged exe is still the applicable update while its replacement downloads — but cleanupStagingOrphans only pinned exes referenced by swap claim files, so a payload older than the grace period was unlinked out from under its own record. Read staged.json itself in the pinning pass so the current record's exe is preserved like any live claim's.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0de8d01e41
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0de8d01e41
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
link() fails with ENOTSUP/ENOSYS/EPERM on FAT/exFAT and some network mounts, which aborted every native update before the download. Add a shared createFileIfAbsent primitive (hard-link a fully written temp file, falling back to an exclusive create + write) and use it for the install lock, its takeover marker, and the swap's claim restore. The fallback's create->write gap is observable, so the lock inspection now grants young unparseable content a publish grace before sweeping it as crash residue.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb71053167
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… claims Two related robustness fixes in the staged swap flow: - A staged executable is now published under a unique per-worker name (kimi-<version>.<pid>.<epoch-ms>.<n>[.exe]) and never replaced; the atomic metadata write retargets the pointer. The pathname a swap validates at claim time can no longer be exchanged by a concurrent same-version publisher between validation and install. - restoreClaimedUpdate only drops the claim when the restore landed or a newer stage holds the state-file path; transient failures retain it. The stale-claim sweep now restores aged claims (create-if-absent) instead of deleting them, so a stage orphaned by a dead swap or a transient restore failure is retried on a later launch.
1 similar comment
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c4cf49cc8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…path waitForStagedUpdate relied on readStagedNativeUpdate, which checks only the recorded size: while a holder re-stages a same-size-corrupted payload (its metadata is replaced only when the repaired generation publishes), a waiter could promote and report the corrupt stage as downloaded, and startup would later reject its checksum. Apply the same integrity bar as stageNativeUpdate's already-staged path — adopt only a payload that hashes to its recorded checksum; a mismatch falls through to the lock poll, which takes over once the holder finishes without repairing it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf5e0c8c01
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…lishes - The fresh-claim sweep is only a directory snapshot: two processes could both pass it before either claimed, then rename the same installed exe concurrently and delete each other's rollback backup. A create-if-absent swap mutex (swap.lock, age-gated like the takeover marker) now serializes the executable-renaming section; the loser restores its claim and defers. The mutex is released as soon as the new exe is in place, before the re-exec, so it is never held for the child session's lifetime. - claimStagedUpdate no longer destroys a claimed record that is unparseable but was young at claim time: on filesystems without hard links the exclusive-create publish is observable mid-write, and discarding it would orphan the staged exe while the writer reports success. Such a record is put back with the same inode so the writer completes it; aged corrupt residue and well-formed records with a missing/changed exe are still discarded.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fbc61f6fcf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The early release let a subsequent swap rename the just-installed exe to the shared .bak path while the previous swap's cleanup was still about to unlink that same path, destroying the second swap's rollback source. The mutex now covers the backup cleanup; the cosmetic staging-dir rmdir and the re-exec stay outside it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cfc6717e52
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Related Issue
No linked issue — the problem is explained below (internal request).
Problem
Native single-binary (SEA) installations cannot self-update on Windows: the update preflight prints
Auto-update is not supported on this platformand tells users to re-runirm … | iexby hand. On Unix, native installs are updated by re-runningcurl … | bash install.sh, which depends on an external script at runtime and needs apipefailworkaround so a failed download is not masked by the pipeline's trailingbashexit status. The result: native users either never update or re-run the installer manually.What changed
Replaces both paths with a staged-swap updater (the Squirrel/NSIS "next launch performs the swap" pattern), shared by Windows and Unix native builds:
__update_download <version>worker (detached in the background, or in the foreground fromkimi upgrade). It downloads the bare per-platform binary from the CDN, verifies it against the release manifest's sha256, and stages it under<exe dir>/.staging/with atomically-written metadata. (Verified end-to-end on macOS against the live CDN: the staged bytes' sha256 matches the published manifest, and the swap re-execs into the real released binary.)rename exe → .bak+rename staged → exe— renaming a running exe is the Windows-safe mechanism install.ps1 already relies on — smoke-checks the new exe with--version, and rolls back from.bakon any failure, so a broken staged binary can never strand the install. On success the process re-execs into the new binary with the original argv; aKIMI_CODE_UPDATE_REEXEC=1guard breaks any swap loop.install.jsonfailure thresholds, rollout gating, and the "updated to vX" notice on the first new-version launch all work unchanged;canAutoInstall('native')is now true on every platform. The pipefail bash / manual PowerShell spawn paths are gone (the install scripts remain the first-install and manual-fallback path).User-facing behavior matches the npm-global flow:
kimi upgrade(or the background updater) downloads, and the next launch runs the new version.Note: Windows rename-in-use semantics are not exercised by CI (macOS/Linux runners). The swap sequence is unit-tested with fake exes and injected spawns, and the macOS real-binary smoke passed (swap + re-exec, rollback on a broken staged binary, downgrade guard). The Windows real-binary run below remains as a handoff item before the next native release.
Windows smoke test (handoff to the Windows owner)
Everything below ran green on macOS with real SEA binaries; what remains is the Windows-only semantics (rename-in-use, locked
.bakcleanup). Steps for a Windows machine (PowerShell, Node 24.15+, pnpm 10.33):Setup — build two versions of the binary:
Stage a fake update:
(
exeSizemust equal the staged file's real byte size — the swap checks it;sha256is verified at download time and not re-checked here.)Cases:
C:\tmp\kimi-fake\bin\kimi.exe --version→ prints99.0.0;.staging\and any.bakare gone; a second run prints99.0.0immediately.copy C:\Windows\System32\whoami.exe C:\tmp\kimi-fake\bin\.staging\kimi-99.1.0.exewith"version": "99.1.0"and the matchingexeSize) → the launch prints the OLD version and%USERPROFILE%\.kimi-code\updates\install.jsongains alastFailurefor99.1.0;.staging\is cleaned. Tip: set$env:KIMI_CODE_HOME = 'C:\tmp\kimi-home'first to keep the state file out of your real profile."version"equal to the in-service build → the launch keeps the old exe and discards the staged files.kimi.exerunning in terminal A (any screen is fine — the process just needs to hold the image), then stage and launch from terminal B → B swaps and prints the new version while A keeps running unaffected. A.bakthat cannot be deleted while A holds it is expected; it must be cleaned on the first launch after A exits.kimi.exe upgrade→ choose "Install update now" → the next launch prints the CDN's latest version.Acceptance: cases 1–4 pass; case 4's leftover
.bakis removed on the first launch after the old session exits.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update (no user-facing config or documented behavior statement becomes stale: the docs never claimed native auto-update was unsupported, and the staged files live next to the installer's exe, outside the documented data-root layout).