Fix self-host/Cloudflare update card always showing; show version in the dashboard#1204
Conversation
…dashboard The self-host and Cloudflare builds baked a placeholder VITE_APP_VERSION (0.0.0-selfhost / 0.0.0-cloudflare), so the update check always compared as "behind" and the card showed even on the latest version. Bake the real release version from the CLI package the same way apps/local does. Also surface the running version in the multiplayer shell sidebar footer so self-hosters can see what they are on. Adds a regression e2e: current build -> no card + version shown.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | 81c1880 | Commit Preview URL Branch Preview URL |
Jun 29 2026, 04:31 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | 81c1880 | Jun 29 2026, 04:32 AM |
Greptile SummaryThis PR fixes the self-host and Cloudflare web shells always showing an "update available" card by replacing baked-in placeholder versions (
Confidence Score: 4/5The production fix is straightforward and correct — baking the real CLI version at build time is exactly how apps/local already works. The only rough edge is in the new regression test, which uses a fixed sleep to guard the negative assertion instead of waiting on a condition. The vite config changes and shell.tsx addition are clean and low-risk. The one issue is a page.waitForTimeout(750) sleep in the new e2e scenario, which could be flaky under load and contradicts the project's explicit no-sleeps testing guideline. e2e/src/update-card-render.ts — the registerUpdateCardCurrentScenario function uses a fixed sleep before its negative assertion. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Build as Vite Build
participant CLI as apps/cli/package.json
participant Define as import.meta.env.VITE_APP_VERSION
participant Shell as shell.tsx (runtime)
participant API as /v1/app/npm/dist-tags
participant Card as SidebarUpdateCard
Build->>CLI: readFileSync("../cli/package.json")
CLI-->>Build: "{ version: "1.5.23" }"
Build->>Define: JSON.stringify("1.5.23")
Note over Define: Statically baked into the bundle
Shell->>Define: read APP_VERSION at module init
Define-->>Shell: "1.5.23"
Shell->>Shell: Render footer v1.5.23 (if APP_VERSION defined)
Shell->>API: fetch dist-tags on mount
API-->>Shell: "{ latest: "1.5.23" }"
Shell->>Card: "compareVersions("1.5.23", "1.5.23") == 0"
Card-->>Shell: hidden (no update needed)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Build as Vite Build
participant CLI as apps/cli/package.json
participant Define as import.meta.env.VITE_APP_VERSION
participant Shell as shell.tsx (runtime)
participant API as /v1/app/npm/dist-tags
participant Card as SidebarUpdateCard
Build->>CLI: readFileSync("../cli/package.json")
CLI-->>Build: "{ version: "1.5.23" }"
Build->>Define: JSON.stringify("1.5.23")
Note over Define: Statically baked into the bundle
Shell->>Define: read APP_VERSION at module init
Define-->>Shell: "1.5.23"
Shell->>Shell: Render footer v1.5.23 (if APP_VERSION defined)
Shell->>API: fetch dist-tags on mount
API-->>Shell: "{ latest: "1.5.23" }"
Shell->>Card: "compareVersions("1.5.23", "1.5.23") == 0"
Card-->>Shell: hidden (no update needed)
Reviews (1): Last reviewed commit: "Fix self-host/Cloudflare update card alw..." | Re-trigger Greptile |
| // The mocked update check resolves on mount; give the state a beat to | ||
| // settle, then assert the card stayed hidden. | ||
| await page.waitForTimeout(750); | ||
| expect( | ||
| await page.getByText("Update available").count(), | ||
| "no update card when the build is current", | ||
| ).toBe(0); |
There was a problem hiding this comment.
Fixed sleep in negative assertion. The
e2e/AGENTS.md guide says "no sleeps; wait on conditions," but page.waitForTimeout(750) is an unconditional 750 ms pause. For the negative case ("card must NOT appear"), Playwright's built-in waitFor({ state: "hidden" }) polls until the element is absent or a timeout fires, giving a deterministic wait without a magic constant that may be too short under load.
| // The mocked update check resolves on mount; give the state a beat to | |
| // settle, then assert the card stayed hidden. | |
| await page.waitForTimeout(750); | |
| expect( | |
| await page.getByText("Update available").count(), | |
| "no update card when the build is current", | |
| ).toBe(0); | |
| // The mocked update check resolves on mount; wait for the card to be | |
| // absent rather than sleeping a fixed duration. | |
| const updateCard = page.getByText("Update available"); | |
| await updateCard.waitFor({ state: "hidden", timeout: 5_000 }); | |
| expect( | |
| await updateCard.count(), | |
| "no update card when the build is current", | |
| ).toBe(0); |
Context Used: e2e/AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Cloudflare previewTorn down — the PR is closed. |
@executor-js/cli
@executor-js/config
@executor-js/execution
@executor-js/sdk
@executor-js/codemode-core
@executor-js/runtime-quickjs
@executor-js/plugin-file-secrets
@executor-js/plugin-graphql
@executor-js/plugin-keychain
@executor-js/plugin-mcp
@executor-js/plugin-onepassword
@executor-js/plugin-openapi
executor
commit: |
|
Verified against the production build (prod (A real container wraps this same build; the web bundle and version injection are identical.) |

Fixes a user report: on a Docker (Railway) self-host, the sidebar "update available" card showed even when running the latest version. Same report asked to surface the version in the dashboard. Both come from one root cause.
Root cause
The self-host and Cloudflare builds baked a placeholder
VITE_APP_VERSION(0.0.0-selfhost/0.0.0-cloudflare) into the web shell. The update card compares that against the publishedexecutordist-tags, socompareVersions("0.0.0-selfhost", "1.5.23")was always-1— permanently "behind", regardless of the deployed version. (The npm CLI and desktop builds already bake the real version, so they were fine.)Fix
apps/host-selfhostandapps/host-cloudflarevite builds now read the real release version from the CLI package, exactly asapps/localdoes. The Docker image and Worker are built at the release tag, so this matches the published version.v<version>(it showed no version before), so self-hosters can see what they are on. Hidden on builds that do not inject a version (e.g. cloud).After the fix: a build that is current shows no card and displays its version; when a newer version is published, the card returns.
Testing
selfhost|cloudflare/update-card-render.test.tsnow has two scenarios each, both green on the real self-host and Cloudflare UIs:Note: deployed instances pick this up on their next image pull / redeploy (the version is baked at build time).