feat: server-build mismatch auto-reload (ready.buildId) - #697
Merged
Conversation
…rtifact-time bake)
….buildId mismatch
…ronments can load ws-handler
…surface their real error
…ence, sync plan to as-built
…ead, archival plan banner
…ted deployments re-arm the guard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Self-heals the stale-client class of failures (the 2026-08-26 "Fresh-agent snapshot response did not match the shared contract" incident): when a browser tab's client bundle was built from a different commit than the server it connects to, the client now detects the mismatch from the WS
readyframe and reloads itself exactly once.How it works
git rev-parse HEAD(literal"unknown"fallback), baked when its artifact is produced:crates/freshell-ws/build.rsbakesFRESHELL_WS_BUILD_COMMIT; the handshakereadystampsbuild_id(serde-omitted whenNone→ frozen transcripts stay byte-valid).build:serverwritesdist/server/build-id.json(scripts/bake-server-build-id.mjs);server/build-id.tsprefers the bake (a stale dist advertises its own build, never the checkout) and fails inert to"unknown"without a valid stamp; tsx-from-source dev mode probes runtime HEAD.definebakes__FRESHELL_BUILD_ID__from the repo HEAD.bootIddoctrine):ReadyMessage.buildId?: string, regeneratedws-server-messages.schema.json(stilladditionalProperties: false), old clients/servers unaffected.src/lib/server-build-check.ts): reload iff both ids present, non-empty, neither"unknown", and different. The sessionStorage sentinel (freshell.server-build-reload) records the attempted server build id — the same server id never reloads twice, a different (corrected) deployment re-arms the guard, a matchingreadyclears it. Any sessionStorage failure = no reload (fail-safe, logged).Test evidence
A→B→B→Cre-arm sequence, throwing-storage + denying-getter fail-safes, at-call-time set-before-reload ordering (18+17 tests).build-idbake precedence/source-vs-compiled split; handshakebuildIdsnapshot stability (both servers).nodeBuildStampIsCurrent) unit-tested 5/5.rust-chromium): exactly-one-reload → real match path clears the sentinel → convergence; persistence across a real navigation; repeat-mismatch suppression. Mandatory three-run RED-verification recorded (green → commented-out red → green).npm run check+npm run test:oracle+npm run test:integrationgreen at HEAD, excluding 5 pre-existing failures that reproduce at base6baeb3e2a(update-flow ×3, visible-first-audit-gate ×2 — proxy-env stderr pollution; recorded in the run ledger).Review provenance
"the-usual" workflow: load-bearing validation (2 falsified assumptions → plan changes), plan review 3 rounds (cap; all blockers fixed), delta review 4 rounds ending PASSED (round 3 fixed the loop-guard's per-server-identity gap). Full artifacts:
.worktrees/.the-usual-logs/server-version-reload/.Caveats
CLOUD_SKIP_SPECSwith justification: the Cloud Run image builds without git metadata, so both stamps are"unknown"there and the compare is inert by design. Coverage lane is localrust-chromium(global-setup fresh-builds both artifacts at HEAD).user.namein the main checkout's.git/config(literaluser.email); commits here carry an explicit correct author.