Refs #1 (Partial) - #2
Conversation
…rift (#1) Source-mode nodes (e.g. the RPi in #1) rebuild apps/web/dist only when someone explicitly runs the web build, separately from `git pull` restarting the server. Since serverVersion/APP_VERSION otherwise track package.json's semver (which doesn't bump per commit on this fork), a stale bundle can silently share a version string with a server that has moved on to a schema-incompatible commit — the existing client/server version-mismatch banner never fires, and the connection just dies with a generic SchemaError. - ServerBuildSha.resolveServerBuildSha resolves the server's own git HEAD SHA at runtime (via the existing ProcessRunner service) and ServerEnvironment suffixes it onto serverVersion when resolvable, falling back to the plain semver for packaged builds without a .git directory (desktop, HA add-on). - vite.config.ts stamps APP_VERSION with the checkout SHA the build ran from, unless an explicit APP_VERSION is provided (release builds). - platform.ts logs a warning (via the existing versionSkew comparison) as soon as the pre-flight environment descriptor is fetched over HTTP, before the WS RPC session is attempted — this is the earliest point a mismatch can be observed, since the WS handshake itself is what fails to decode when client/server drift. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
VERDICT:CHANGES_NEEDED SummaryWell-scoped, well-tested implementation of Option 2 from #1, and the PR description's self-assessment is unusually honest. The Effect idioms are correct, the graceful The blocker is a false-positive regression that the PR's otherwise-thorough analysis doesn't mention. Main issue: SHA-granularity comparison produces false "version drift" on healthy remote environments
Before this PR both sides derived from
This matters because the issue's failure surfaced specifically "while adding a second environment" — the multi-environment path is exactly where the new false positives concentrate. The feature's value is a signal you trust; routinely firing it for healthy remotes invites banner fatigue and erodes the real local-drift signal it's meant to sharpen. The change conflates two distinct conditions — "my local web bundle is stale relative to the server serving it" (the actual bug) vs. "a remote peer is on a different commit" (benign) — and the SHA suffix makes the second trip the same UI as the first. Suggested direction (maintainer's call)Restrict the SHA-granularity comparison to the same-origin / primary connection where local bundle drift is the real risk, and keep remote-peer comparison at semver granularity (or suppress the SHA component for non-primary environments). That preserves the fix for the reported case without lighting up every cross-node connection. Minor notes (non-blocking)
Net: the mechanism is sound and the code quality is high, but the per-environment false positive needs to be addressed (or explicitly accepted by a maintainer) before merge, since it degrades a shared web/mobile/desktop feature in the very scenario the issue describes. |
…y connection The reviewer on #2 flagged a false-positive regression: stamping serverVersion with +git.<sha> made resolveVersionMismatch compare SHAs across every connected environment, not just the primary/local one. A packaged desktop build (no .git, plain semver) or any remote peer on a different commit now tripped the "version drift" banner even though both were perfectly healthy — exactly the multi-environment path the original issue's failure mode was reported on. resolveVersionMismatch/resolveServerConfigVersionMismatch now compare at release granularity by default (git build metadata stripped) and only opt into exact SHA comparison via `{ compareBuildMetadata: true }` for call sites that are provably the primary/same-origin connection: - ConnectionsSettings.tsx's primary banner (opts in) - ChatView.tsx's per-thread banner (opts in only when the active thread's environment is the primary one) - connection/platform.ts's pre-flight descriptor check (opts in — both its callers bootstrap a same-machine connection built from this client's own checkout) Saved/remote environment rows in ConnectionsSettings.tsx keep the new default (no option passed), matching pre-#1 semver-only behavior. Also addresses the review's two minor notes: dropped the redundant /i flag on the already-lowercased SHA regex (server and vite.config.ts), and gated vite.config.ts's git rev-parse spawn to `command === "build"` so dev/test invocations no longer spawn it on every run.
Addressed review feedback (commit e8f275f)Main issue — SHA-granularity false positive on healthy remotes: fixed.
Added test coverage in Minor notes: both addressed.
Verified:
|
|
VERDICT:APPROVED SummaryThis is a well-scoped, well-tested implementation of Option 2 from #1, and the prior-review blocker has been correctly resolved in commit The prior blocker is genuinely fixedThe earlier review flagged that raw SHA-granularity comparison would fire false "version drift" banners on healthy remote/desktop connections — precisely in the multi-environment path the issue describes ("while adding a second environment"). The current diff resolves this correctly:
Correctness of the core mechanism
Tests
Minor, non-blocking
Net: the mechanism is sound, the earlier false-positive regression is properly fixed and scoped, code quality is high, and the deliberate scope (Option 2 only) matches the issue's own framing. Approving. |
What this does
Implements Option 2 from #1 ("version handshake so drift fails loudly"), which the issue calls the best value-per-effort direction even if the rest is deferred.
Root cause recap: on source-mode nodes (e.g. the RPi),
apps/web/distis a static bundle that's only rebuilt when someone explicitly runs the web build —git pull+ server restart doesn't touch it.serverVersion/APP_VERSIONare both derived frompackage.json's semver, which doesn't bump per commit on this fork, so a stale bundle can silently share the exact same version string as a server that has since moved on to a schema-incompatible commit. The existing client/server version-mismatch banner (versionSkew.ts— it already existed, wired into Sidebar/ChatView/ConnectionsSettings) never fires, and the WS RPC connection just dies with a genericSchemaErroron every attempt.Changes:
apps/server/src/environment/ServerBuildSha.ts(new): resolves the server's own git HEAD SHA at runtime via the existingProcessRunnerservice, falling back tonullon any failure (no.git, nogitbinary, timeout, non-repo).apps/server/src/environment/ServerEnvironment.ts: suffixes that SHA ontoserverVersion(0.0.28+git.<sha12>) when resolvable. Packaged builds without a.gitdirectory (desktop DMG, HA add-on) are unaffected — they already build server+web together per the issue's own deploy matrix.apps/web/vite.config.ts: stampsAPP_VERSIONwith the checkout SHA the build ran from, unless an explicitAPP_VERSIONenv var is set (release builds keep their existing behavior). Useseffect/unstable/process/ChildProcessrather than rawnode:child_processper this repo'snodeBuiltinImportlint rule.apps/web/src/connection/platform.ts: logs a warning via the existingresolveVersionMismatchcheck as soon as the pre-flightGET /.well-known/t3/environmentdescriptor is fetched over plain HTTP — before the WS RPC session is attempted. This matters because the WS handshake's first call (server.getConfig) is exactly what fails to decode when client/server drift, so a mismatch signal has to come from somewhere that resolves independently of that fragile channel to be useful for this specific failure mode.This reuses all the existing version-mismatch UI (banners, dismissal/localStorage, hints) with zero new schema fields —
serverVersionitself now just carries more signal.Why "Refs #1 (Partial)" and not "Fixes #1"
The issue opens with three directions and explicitly says "opening for design discussion," not "please implement X." I only implemented Option 2, on purpose:
scripts/deploy-node.shfull pull→install→build→restart sequence) and Option 3 (CI-built artifacts distributed to nodes). Both are bigger, riskier changes that the issue itself flags as "larger effort" / "deferred," not something to unilaterally decide in this PR.primaryServerConfigAtom(which the existing UI banners read from) never populates, since it's only set after a successfulserver.getConfiground-trip. In that scenario the new pre-flight HTTP-descriptor check still fires and logs a clear, correlated warning (visible in console/observability), but it won't produce a UI banner on its own. Wiring the pre-flight descriptor's version into UI state independent of the WS session would close this gap but touches connection-state plumbing shared across web/mobile/desktop; I judged that out of scope for one focused PR and didn't want to guess at the right cross-platform design under limited time.Testing
pnpm --filter t3 test/pnpm --filter @t3tools/web test— full suites pass (oneMessagesTimeline.test.tsxtimeout is pre-existing flakiness under load; verified it passes standalone and is unrelated to this diff — untouched code path).pnpm --filter t3 --filter @t3tools/web --filter @t3tools/contracts typecheck— clean.vp linton all changed files — clean.apps/web/vite.config.tsactually loads and builds under the new top-levelawait(ranvite build --mode developmentto the transform stage).apps/server/src/environment/ServerBuildSha.test.ts(5 cases: success, non-zero exit, timeout, malformed output, spawn failure).