Release v0.51.285 — Release JA (stage-r19 — update-reload server-identity race fix #3654) - #3693
Conversation
…e reload (#3654) Replaces the raw-uptime comparison (couldn't distinguish a fresh old process from the restarted one) with a stable server_started_at identity read before the update POST; reloads only when the identity changes. Both the force-update and regular apply paths read + pass the baseline. (#874, #3654) Co-authored-by: Frank Song <franksong2702@gmail.com>
|
| Filename | Overview |
|---|---|
| static/ui.js | Core logic change: adds _normalizeHealthServerIdentity, _readHealthServerIdentity, and rewires _waitForServerThenReload to gate reload on a process-identity change rather than any healthy response; both applyUpdates and forceUpdate correctly capture the baseline before their POST calls. |
| api/routes.py | Adds server_started_at (=SERVER_START_TIME, a module-level time.time()) to the /health payload; the value is always a valid float, stable across the process lifetime, and guaranteed to change on restart. |
| tests/test_update_banner_fixes.py | Adds four new tests: structural source assertions for baselineServerIdentity capture and comparison patterns, plus two subprocess/Node.js integration tests validating the null-baseline fallback and the same-identity-skip/new-identity-reload behaviour directly. |
| tests/test_update_apply_ui.py | Narrow fix: relaxes the _waitForServerThenReload() source-text assertion from the exact call signature to the bare function name to accommodate the new {baselineServerIdentity} argument. |
| CHANGELOG.md | Adds v0.51.285 release entry describing the server-identity reload fix; no code changes. |
Sequence Diagram
sequenceDiagram
participant UI as Browser (ui.js)
participant H as /health
participant API as /api/updates/*
Note over UI: applyUpdates() or forceUpdate()
UI->>H: GET /health (cache:no-store)
H-->>UI: "{ server_started_at: T1, status: ok }"
Note over UI: baselineServerIdentity = normalize(T1)
UI->>API: POST /api/updates/apply (or /force)
API-->>UI: 200 OK
Note over UI: _waitForServerThenReload({baselineServerIdentity})
loop Poll every 500 ms (max 15 s)
UI->>H: GET /health
alt Server still old process
H-->>UI: "{ server_started_at: T1, status: ok }"
Note over UI: nextIdentity == baseline, keep polling
else Server restarted (new process)
H-->>UI: "{ server_started_at: T2, status: ok }"
Note over UI: nextIdentity != baseline, location.reload()
else baseline was null (fallback)
H-->>UI: "{ status: ok }"
Note over UI: reload immediately on first healthy response
end
end
Note over UI: Deadline reached, show manual-reload warning
Reviews (1): Last reviewed commit: "docs(changelog): v0.51.285 — Release JA ..." | Re-trigger Greptile
…tity race fix nesquena#3654) (nesquena#3693) * Fix update reload readiness race — poll /health server identity before reload (nesquena#3654) Replaces the raw-uptime comparison (couldn't distinguish a fresh old process from the restarted one) with a stable server_started_at identity read before the update POST; reloads only when the identity changes. Both the force-update and regular apply paths read + pass the baseline. (nesquena#874, nesquena#3654) Co-authored-by: Frank Song <franksong2702@gmail.com> * docs(changelog): v0.51.285 — Release JA (stage-r19) --------- Co-authored-by: Frank Song <franksong2702@gmail.com> Co-authored-by: nesquena-hermes <[email protected]>
Release stage-r19 (v0.51.285) — update-reload server-identity race fix (#3654)
UN-HOLD re-gate. I previously HELD #3654 (this session) because its update-reload logic
compared raw
/healthuptime, which can't distinguish a still-running old process from therestarted replacement — a chain of issues ending in a design-level concern that needed a
backend
/healthchange. @franksong2702 reworked it to the requested approach.The original hold finding (now reworked)
_readHealthUptimeSeconds()raw-uptime<comparison; null baseline defeated byNumber(null)===0; force-update read the baseline AFTER the POST./healthnow exposesserver_started_at(=
SERVER_START_TIME, the process-start timestamp). Client reads_readHealthServerIdentity()BEFORE the update POST, stores it asbaselineServerIdentity,and
_waitForServerThenReload()reloads only whennextServerIdentity !== baselineServerIdentity(a genuinely new process), with a
baselineServerIdentity===null→ reload-anyway fallback._normalizeHealthServerIdentitycoerces string|numeric, returns null only on missing/empty(NOT on 0). BOTH reload paths (force-update
forceUpdate+ regularapplyUpdate) read +pass the baseline — no sibling-path gap.
What to check
can't false-positive (reload against the OLD process) or hang forever (never reload). Verify
the null-baseline fallback and the deadline/maxMs timeout behave. Confirm no NEW regression in
the update-apply flow. SAFE TO SHIP or MUST-FIX.
both reload call sites pass baseline;
server_started_atis stable across the process lifetimeand changes on restart. Focused code read + one small targeted check only; do NOT write
extensive reproduction harnesses.
Files: api/routes.py (+server_started_at in /health), static/ui.js (identity read/compare),
tests/test_update_apply_ui.py, tests/test_update_banner_fixes.py.