mirror of
https://github.com/cline/cline.git
synced 2026-09-01 15:11:04 +08:00
d3d3bd8749
* fix(core): bridge protections for updates landing under pre-3.0.55 clients Three pieces, each proven against real released artifacts: - postinstall shield: CLI versions <= 3.0.54 restart the hub daemon after a background auto-update even while it serves live sessions, and their fingerprint check then rejects every replacement hub, bricking the running TUI. That code is on users' machines and cannot be patched — but it runs only after the install completes, and it bails out harmlessly when no hub discovery record exists. The newly installed package's postinstall sets the record aside so the old updater never fires. - superseded-record fallback: the set-aside record is also the only source of the auth token and pid the next new-build launch needs to retire the displaced hub (a port probe carries neither); ensure reads it back. - bind retry: a hub retired on the fixed port can hold it ~2s after acking shutdown (watchdog force-exit); the replacement daemon retries EADDRINUSE for up to 5s instead of dying and leaving no hub at all. * fix(cli): defer auto-update install until no CLI is attached to the hub Installing while cline processes run swaps the npm package under them: their respawn paths break on the new build fingerprint, and the updater then restarted the hub daemon out from under live sessions (the 'Hub connection closed (code=1006)' incident). Guarding the restart treats the symptom; the fix is to never install under a running process. The startup check now only records that an update is available. The install runs at process exit, and only when the hub confirms no other cli* client is attached — desktop sidecars and connectors ship their own binaries, so only cli* clients make the swap unsafe. With nothing old running at install time, no hub restart is needed at all: the next launch retires the stale hub through the existing ensure path. Deletes restartHubServerIfRunning, ensureCliHubServerAfterUpdate, and their support code; manual 'cline update' still installs immediately and now just notes that the update applies on next start. * fix(cli): apply deferred update from the entrypoint exit sequence The CLI entrypoint always terminates with an explicit process.exit(), which never emits beforeExit — the hook the deferred installer waited on, so it would never have run (caught by review). Invoke applyDeferredUpdate directly from the entrypoint's exit sequence after disposeAll(), where every normal termination passes; crash paths deliberately skip it. Also clear the pending update once an install spawns so the apply is idempotent. * test(cli): isolate unit tests from the real ~/.cline A full vitest run could leave a real hub daemon running against the developer's actual ~/.cline discovery record (observed while validating this PR: a daemon spawned from the globally installed cline binary, attached to the real data dir). Point CLINE_DIR, CLINE_DATA_DIR, and CLINE_HUB_DISCOVERY_PATH at a per-worker temp dir and disable auto-update before any test file loads; subprocesses inherit the isolation via env. * fix(core): discard the superseded discovery record once consumed The set-aside record is one-shot recovery metadata, but nothing deleted it, and it feeds a pid into retireDiscoveredHub's SIGTERM. Weeks later a launch that finds no live record (routine after any retirement) could read the stale file and signal whatever process the OS recycled that pid onto (review finding by @abeatrix). Unlink it at every ensure resolution that ends with a live, verified hub; failure paths keep it for the next attempt. * fix(cli): harden the exit-time update gate Three review findings on the deferred-apply path: - A wedged hub could stall an otherwise-finished CLI for tens of seconds via the hub client's default timeouts; the whole exit-time query is now bounded to 3s, with timeout counting as attached (never install unless the hub positively confirms). - Sub-second commands exited before the startup version check resolved and silently dropped the update every time for one-shot-only usage; exit now grants the in-flight check a 250ms grace. - client.list can lose a TUI's registration during transport churn while its session connection survives, so an empty client list is not proof of safety; cross-check sessions with participants. Participants rather than session status: finished sessions linger idle forever and must not pin updates, and participant-less scheduled runs live in the hub process, which the binary swap does not touch. Verified live: a session-holding client invisible to client.list defers the install, and the gate opens once it disconnects. * docs(cli): fix stale beforeExit reference in the exit-gate comment * style(cli): apply biome formatting to update deferral code * fix(cli): let doctor see a hub whose record the update shield set aside During the shielded update window the discovery record is renamed to .superseded so pre-3.0.55 updaters cannot restart a busy hub. Doctor read only the primary record, so in that window it reported the live daemon - the one serving the user's still-open old session - as a stale hub daemon and advised 'cline doctor fix', which kills it and reproduces the exact 1006 incident the shield exists to prevent (found by QA). Doctor now falls back to the set-aside record the same way the ensure path does, and doctor fix clears the set-aside file along with the primary record so a deliberate reset does not leave stale retirement metadata pointing at a recyclable pid. * fix(core): keep shielded sessions on one Hub authority (#13244) * fix(core): recover shielded busy hub discovery * chore(core): instrument shielded hub recovery * fix(core): recover shielded hubs with attached clients * fix(cli): recognize shielded hubs in doctor * refactor(core): keep shield recovery minimal * fix(core): retain shared Hub idle helper semantics * chore(core): align busyness helper with the #13231 wording The participants-only hasActiveHubSessions here duplicates the change on bee/hub-lifecycle (this branch needs its semantics for the participant gate). Matching that version byte for byte lets the two merges resolve cleanly instead of conflicting. Also restores the module-registry reset comment this branch dropped - it documents a real local-vs-CI gotcha.