fix(deploy): serve fresh dashboard on hosted deploys + staleness guardrail - #227
Merged
Merged
Conversation
aterrylu
enabled auto-merge (squash)
June 20, 2026 07:49
…drail
The hosted server prefers packages/server/src/_embedded_dashboard over
packages/dashboard/dist (run.ts), but that embedded copy is a *binary-build*
artifact — and `make deploy` was rsyncing a stale local copy of it to the
remote while `make prod` only rebuilt dist. Result: the hosted (tsx) server
served a stale dashboard that shadowed the freshly-built dist, silently. It
caused an actual regression (forge served a months-old UI) and needed manual
patching multiple times.
Fix (keep embedded a binary-only concern; hosted serves dist):
- make prod: `rm -rf packages/server/src/_embedded_dashboard` before the vite
build, so the hosted/tsx server falls through to the fresh dist.
- make deploy: exclude `_embedded_dashboard` from the rsync, so the local
binary artifact is never shipped to the remote.
Guardrail (make staleness observable, not silent):
- Server reads the served dashboard's bundle id (hashed index-*.js filename)
and index.html mtime, logs them at startup, and exposes them on /api/host
as `dashboard: { build, builtAt }`.
- Dashboard reads its own loaded bundle id and console.warns when the server
is serving a newer one (deploy-while-open, or a stale serve).
Tests: extractDashboardBuildId (server), getLoadedDashboardBuildId +
isDashboardStale (dashboard).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwFzy8JiM6H2NENce8Hmpx
aterrylu
force-pushed
the
terry/hosted-deploy-fresh-dashboard
branch
from
June 20, 2026 07:49
05adf3c to
019d215
Compare
nox-0x
approved these changes
Jun 20, 2026
nox-0x
left a comment
Collaborator
There was a problem hiding this comment.
Approving — fix correctly attacks both legs of the stale-serve bug (rsync exclude on the source side, rm -rf on the build side) and the staleness guardrail is well-bounded: isDashboardStale only fires when both ids are known, the /api/host payload addition is non-breaking for existing consumers (CLI status, ConnectionStatusBarItem), and the unauthenticated endpoint only leaks a bundle hash + build mtime. Tests cover the truth table and the failure-logs-not-throws case.
A couple of follow-up observations (non-blocking):
- The staleness check in StatusBar runs once on mount; the PR description says it catches "deploy-while-open" but in practice it only catches "reload-after-stale-deploy". Worth either qualifying the claim or piggy-backing on ConnectionStatusBarItem's existing /api/host poll if you want true mid-session detection.
extractDashboardBuildId's regex relies on the entry script being the first/assets/index-*.jsreference in the HTML, which holds for current Vite output but would silently drift if Vite ever reorders modulepreload links before the entry. The DOM-side selector is already more precise (script[type="module"]); tightening the server regex similarly (e.g. requiring<script type="module"[^>]*src="...") would make the two readers agree by construction. Not urgent.
This was referenced Jun 20, 2026
aterrylu
added a commit
that referenced
this pull request
Jun 20, 2026
Captures the decision behind PR #227's deploy fix: _embedded_dashboard is a binary-distribution artifact; the hosted server serves dashboard/dist. Enforced via rm in make prod + rsync --exclude in make deploy, with observability guardrails (served bundle id + mtime in /api/host, dashboard console.warn on mismatch). Records the load-bearing role of the rm (self-heals stale remotes), the long-term direction toward install.sh-built-bundle convergence, and the two operational issues surfaced during forge deployment. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.
Problem
Hosted (
make deploy) deployments were silently serving a stale dashboard. The server preferspackages/server/src/_embedded_dashboard/overpackages/dashboard/dist/(run.tsresolution order), but_embedded_dashboardis a binary-build artifact — and the deploy pipeline mishandled it two ways:make deploy's rsync shipped a stale local_embedded_dashboardto the remote (it lives undersrc/, not excluded alongsidedist/node_modules).make prodrebuiltdashboard/distbut never refreshed the embedded copy.Net: the freshly-built
distsat unused while the server served a months-old embedded bundle — it caused an actual regression on forge and needed manual patching multiple times.flowchart LR subgraph before["Before"] L["local stale<br/>_embedded_dashboard"] -->|rsync ships it| R1["remote _embedded_dashboard (stale)"] MP1["make prod → dist (fresh)"] --> D1["dist (fresh, unused)"] R1 -->|server prefers #1| S1["serves STALE ❌"] end subgraph after["After"] MP2["make prod: rm -rf _embedded_dashboard<br/>then build dist"] --> D2["dist (fresh)"] D2 -->|"no embedded → fallback #2"| S2["serves FRESH ✅"] end style S1 fill:#fdd style S2 fill:#dfdFix — keep embedded a binary-only concern; hosted serves
distmake prod:rm -rf packages/server/src/_embedded_dashboardbefore the vite build → the hosted/tsx server falls through to the freshdist. (Runs on the remote duringmake deploy, so it also self-heals any pre-existing stale copy there.)make deploy:--exclude _embedded_dashboardfrom the rsync → the local binary artifact is never shipped.Guardrail — make staleness observable, not silent
The recurring pain was diagnosing stale serves. Now:
dashboardBuild.ts+run.ts): reads the served bundle id (hashedindex-*.js) +index.htmlmtime; logs them at startup and exposes them on/api/hostasdashboard: { build, builtAt }. The best-effort reader now logs on failure (it's only called afterexistsSyncconfirmedindex.html, so a throw is a real read/permission problem — not a benign null).dashboardFreshness.ts+StatusBar.tsx): reads its own loaded bundle id andconsole.warns when the server is serving a newer one (deploy-while-open, or a stale serve). Unknown ids (dev server / older server) never warn.Testing
make checkgreen: biome, tsc, server tests (incl.extractDashboardBuildId+ thereadDashboardBuildfailure-logs-and-doesn't-throw case), 246 dashboard tests (incl.isDashboardStaletruth table +getLoadedDashboardBuildIdDOM reads)./polish(code-reviewer + simplifier + silent-failure-hunter): reviewer ship-ready (verified Makefile flow, non-breaking/api/host, no new leak on the unauthenticated endpoint); simplifier no-change; silent-failure-hunter's one MEDIUM (barecatchswallowing the reason) applied._embedded_dashboardpresent, startup log shows today's build mtime,/api/hostcarriesdashboard.Operational note
forge's duplicate install was cleaned up out-of-band as part of this work: a crash-looping systemd-user
autonomos.service(running a stale~/.local/share/autonomosCLI install) was fighting pm2 for:3100; it's been stopped/disabled/removed so a single mechanism (pm2) owns the port.~/.autonomosconfig/data preserved.ADR
This encodes a deploy-contract decision (the embedded dashboard belongs to the binary distribution; the hosted server serves
dist). An ADR draft is being surfaced to TeamLead.🤖 Generated with Claude Code