Skip to content

feat(kimi-code): support automatic updates for native installations via staged swap - #2994

Merged
liruifengv merged 45 commits into
mainfrom
feat/native-staged-auto-update
Aug 18, 2026
Merged

feat(kimi-code): support automatic updates for native installations via staged swap#2994
liruifengv merged 45 commits into
mainfrom
feat/native-staged-auto-update

Conversation

@liruifengv

@liruifengv liruifengv commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

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 platform and tells users to re-run irm … | iex by hand. On Unix, native installs are updated by re-running curl … | bash install.sh, which depends on an external script at runtime and needs a pipefail workaround so a failed download is not masked by the pipeline's trailing bash exit 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:

  • Stage in the background, never touch the running exe. The update preflight now self-spawns a hidden __update_download <version> worker (detached in the background, or in the foreground from kimi 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.)
  • Swap at the very top of startup, then re-exec. When staged metadata is present, the next launch claims it atomically (rename, so concurrent instances elect a single winner), refuses anything not strictly newer than the running version, swaps via 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 .bak on 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; a KIMI_CODE_UPDATE_REEXEC=1 guard breaks any swap loop.
  • Reuses the existing update machinery. Install lock, install.json failure 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 .bak cleanup). Steps for a Windows machine (PowerShell, Node 24.15+, pnpm 10.33):

Setup — build two versions of the binary:

git checkout feat/native-staged-auto-update
cd apps\kimi-code
# Bump "version" in package.json to 99.0.0, then:
pnpm build:native:sea
copy dist-native\bin\win32-x64\kimi.exe C:\tmp\kimi-new.exe
# Restore package.json, rebuild the "old" binary, install it as the in-service exe:
pnpm build:native:sea
mkdir C:\tmp\kimi-fake\bin
copy dist-native\bin\win32-x64\kimi.exe C:\tmp\kimi-fake\bin\kimi.exe

Stage a fake update:

mkdir C:\tmp\kimi-fake\bin\.staging
copy C:\tmp\kimi-new.exe C:\tmp\kimi-fake\bin\.staging\kimi-99.0.0.exe
$size = (Get-Item C:\tmp\kimi-fake\bin\.staging\kimi-99.0.0.exe).Length
@"
{
  "version": "99.0.0",
  "target": "win32-x64",
  "exeFileName": "kimi-99.0.0.exe",
  "sha256": "$('a' * 64)",
  "exeSize": $size,
  "stagedAt": "2026-08-17T00:00:00.000Z"
}
"@ | Set-Content C:\tmp\kimi-fake\bin\.staging\staged.json

(exeSize must equal the staged file's real byte size — the swap checks it; sha256 is verified at download time and not re-checked here.)

Cases:

  1. Swap + re-exec: C:\tmp\kimi-fake\bin\kimi.exe --version → prints 99.0.0; .staging\ and any .bak are gone; a second run prints 99.0.0 immediately.
  2. Rollback: re-stage a broken binary (e.g. copy C:\Windows\System32\whoami.exe C:\tmp\kimi-fake\bin\.staging\kimi-99.1.0.exe with "version": "99.1.0" and the matching exeSize) → the launch prints the OLD version and %USERPROFILE%\.kimi-code\updates\install.json gains a lastFailure for 99.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.
  3. Downgrade guard: stage with "version" equal to the in-service build → the launch keeps the old exe and discards the staged files.
  4. Rename-in-use (the Windows-specific one): keep an OLD kimi.exe running 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 .bak that cannot be deleted while A holds it is expected; it must be cleaned on the first launch after A exits.
  5. Foreground flow (optional, hits the real CDN): with an older in-service build, 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 .bak is removed on the first launch after the old session exits.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, 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).

…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-bot

changeset-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f576043

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

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

@pkg-pr-new

pkg-pr-new Bot commented Aug 17, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@f576043
npx https://pkg.pr.new/@moonshot-ai/kimi-code@f576043

commit: f576043

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-swap.ts Outdated
Comment thread apps/kimi-code/src/cli/update/preflight.ts
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.
…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).
… order

The restore-on-failure case now observes the early smoke check's
--version spawn; only the re-exec spawn must be absent.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

Addressed both Codex findings in e8284b6ab (plus merge 5ee963bbf and test alignment 993dafa72):

P1 — executable path recoverability during swap: the --version smoke check now runs on the staged exe before anything is moved, so a bad staged binary is discarded with the install path never left empty. The residual crash window is the two adjacent atomic renames themselves (exe → .bak, staged → exe); on POSIX/NTFS each is atomic, and recovery is mv <exe>.bak <exe> or re-running the install script — this is documented in a comment at the swap site. A fully self-healing empty-exe path would require an external updater process, which is out of scope here (same residual risk profile as rustup-style self-rename updaters).

P2 — lock contention with the spawned worker: startBackgroundInstall no longer takes the outer install lock for the native source. The self-spawned __update_download worker holds the lock for the entire download (strictly stronger than the parent's spawn-moment guard), so the child can no longer exit-0 without staging and get misreported as lastSuccess.

Also merged 6a21aed7e (restore claimed metadata on rename failure, stale-claim/bak sweep at startup, read-once re-exec guard, 30 s Windows smoke timeout) and aligned its restore-path test with the smoke-before-rename order. Full suite: 206 update-domain tests green, typecheck + oxlint clean.

@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/sub/update-download.ts Outdated
Comment thread apps/kimi-code/src/cli/update/native-stage.ts Outdated
Comment thread apps/kimi-code/src/cli/update/native-swap.ts Outdated
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
@liruifengv

Copy link
Copy Markdown
Collaborator Author

Second review round addressed in f04b4c057:

Fixed (3):

  • Signal termination from the re-exec'd CLI (native-swap.ts): the exit-code mapping now follows the shell convention — 128 + signo (e.g. SIGKILL → 137), falling back to 1 — so a killed child is never reported as a successful invocation.
  • False success when the install lock is held (update-download.ts): the worker now reads the lock holder's version (readUpdateInstallLockVersion). Exit 0 without staging only when the holder is staging the same version (its outcome is ours); a different in-flight version exits 1 so foreground kimi upgrade shows a failure instead of a success message; a lock that vanished between the two reads triggers one acquire retry.
  • Orphaned staging artifacts (native-stage.ts): staging now sweeps unreferenced files (interrupted .part downloads, staged exes whose staged.json never landed) before downloading — each orphan was ~180 MB accumulating indefinitely. Live swap claims (staged.json.swap-*) and the exes they reference are preserved (claim payload read with a basename() guard against path escape).

Not changed (2):

  • P1 swap crash window: same finding as round 1. The window is now two adjacent atomic renames with the smoke check before both; there is no userspace primitive that replaces a running exe atomically (rustup-style updaters accept the same window), and recovery is documented at the swap site (mv <exe>.bak <exe> or re-run the install script).
  • P2 lock contention in startBackgroundInstall: stale — the native path has not held the outer lock since e8284b6ab (preflight.ts:572-580); the worker owns the lock for the full download.

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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-manifest.ts Outdated
@liruifengv

Copy link
Copy Markdown
Collaborator Author

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 → lastSuccess recorded → next launch swapped and re-exec'd into the official 0.36.1 binary (final exe sha256 matches the CDN manifest). Foreground kimi upgrade path verified earlier the same way. Both work.

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 active record blocks all background retries for 6 hours, silently.

Observed sequence:

  1. First launch spawned the background downloader (active written ~2 s after start). The downloader then died before doing any work — no .staging, no log line. Most likely environmental: first run of a freshly-copied unsigned ~147 MB exe, and a hidden detached self-spawn is a classic AV heuristic trigger. It died too early to log anything, so this can't be confirmed from logs.
  2. The TUI exited before the child's exit event, so finish() never ran: no lastFailure, no lastSuccess. The active record stayed behind.
  3. Every subsequent launch hit hasFreshActiveInstall() → skipped re-staging. With AUTO_INSTALL_ACTIVE_TTL_MS at 6 h, auto-update silently did nothing — no prompt, no message. (Manual kimi upgrade still works; it doesn't consult active.)

Two improvement ideas (fine as a follow-up, doesn't have to gate this PR):

  • Let the native downloader record its own outcome. __update_download already holds install.lock for the whole download; having it write lastSuccess/lastFailure into install.json itself (mirroring how the swap self-records via recordSwapFailure) would make the bookkeeping independent of the parent's lifetime.
  • Treat an active record as stale when no install is actually in flight. For native installs, "in flight" ≈ "install.lock is currently held" — by the time preflight runs, any completed staging would already have been swapped in, so active + no live lock ⇒ orphaned; convert it to a failure (or clear it) instead of waiting out the 6 h TTL. Package-manager sources can't use the lock as a liveness signal and may need a shorter TTL instead.

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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

Third review round addressed in 3cb0cc093:

Fixed — timeout now covers body consumption (native-manifest.ts, native-stage.ts): the manifest fetch keeps its AbortController armed through response.text() (headers-then-stall can no longer hang the worker), and the binary stream aborts after 30 s without a chunk — idle-bounded, not duration-bounded, so slow networks still finish. The idle timeout is injectable; the stall path is covered by a test that verifies the worker fails fast and cleans up staging leftovers.

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).
@liruifengv

Copy link
Copy Markdown
Collaborator Author

Thanks for the Windows real-machine run — great to have the background flow and the foreground kimi upgrade both confirmed end-to-end against the live CDN, exe sha256 matching the manifest.

The orphaned active record is fixed in ef97ea38b, going with your option (b) rather than (a):

  • Why not (a): a downloader writing its own outcome only helps when the child outlives the parent. Your scenario was the opposite — the child died before doing any work, so it would never have written anything either. Self-recording also splits bookkeeping across parent and child.
  • What (b) looks like: past a 60 s spawn grace window (the worker needs a moment to self-acquire the lock — judging earlier would race it), lock liveness is the truth for native installs. active + lock held ⇒ a download is genuinely in flight, trust it; active + lock free ⇒ orphaned record, start a new attempt instead of waiting out the 6 h TTL. The stale-lock sweep (30 min) covers a crashed downloader that never released. Package-manager sources have no liveness signal, so they keep the existing TTL behavior.
  • Wired at both decision points — the outer tryStartAutomaticBackgroundInstall check mattered most: an orphan was short-circuited there as "already handled" and never reached the inner check.

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.

@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-swap.ts Outdated
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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

1 similar comment
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-stage.ts
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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-stage.ts
…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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-stage.ts
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/install-lock.ts Outdated
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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-swap.ts Outdated
Comment thread apps/kimi-code/src/cli/update/native-swap.ts Outdated
… 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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

1 similar comment
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/sub/update-download.ts Outdated
…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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-swap.ts
Comment thread apps/kimi-code/src/utils/persistence.ts
…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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-swap.ts Outdated
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.
@liruifengv

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/kimi-code/src/cli/update/native-swap.ts
Comment thread apps/kimi-code/src/cli/update/native-swap.ts
@liruifengv
liruifengv merged commit 8c865f4 into main Aug 18, 2026
15 checks passed
@liruifengv
liruifengv deleted the feat/native-staged-auto-update branch August 18, 2026 09:17
@github-actions github-actions Bot mentioned this pull request Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant