fix(fleet): resolve version detection using package.json over stale generated constant (#410)

* fix(fleet): resolve stuck update states and improve update UX

The fleet node update flow had several bugs: the in-memory update tracker
never cleared terminal states (timeout, failed, completed), leaving nodes
permanently stuck with no way to retry or dismiss. The Recheck button
only re-fetched stale state without clearing it, and the POST trigger
rejected retries with 409 even after timeout.

Backend fixes:
- Add DELETE endpoints (single node + batch) to clear tracker entries
- Fix 409 race: detect expired timeouts and clear terminal states before
  re-triggering
- Populate error messages in the tracker for timeouts and failures
- Include error field in the update-status API response
- Auto-expire completed entries after 60 seconds

Frontend fixes:
- Add retry (RotateCcw) and dismiss (X) buttons on failed/timed-out badges
- Show error details via animated cursor hover (CursorFollow pattern)
- Recheck button now batch-clears all terminal states before fetching
- Recheck shows loading spinner and disables while checking
- Extract NodeCardProps interface for readability

* fix(fleet): detect update completion via process start time

Remote nodes that cannot report their version (e.g. older builds)
caused updates to always time out because completion detection
relied solely on version comparison. The gateway now tracks the
remote node's process start time from /api/meta and detects
container restarts by comparing it across polls.

Also extracts a createTracker() factory to eliminate repeated
object construction across 5 call sites.

* docs: add troubleshooting for first-update timeout on old nodes

Adds a new troubleshooting entry explaining why the first remote
update on nodes running pre-v0.40.0 always times out (neither
version nor process start time can be detected). Documents the
fix: dismiss, recheck, and confirm the node updated.

Also adds a screenshot of the timed-out state with retry/dismiss
buttons to the remote updates feature page.

* fix(fleet): detect update completion via offline detection and error reporting

The update completion detection relied on version change and process
start time, both of which fail on nodes running older Sencho versions
that report "unknown" and lack the startedAt field. This caused every
update to time out after 5 minutes.

Add three-signal detection: version change, process restart (startedAt),
and offline/online detection (node went unreachable during update and
came back). Also add a 90-second early failure heuristic for when the
remote image pull fails silently, and surface pull errors from
SelfUpdateService via /api/meta so the gateway can report them
immediately.

* fix(deps): bump vite to 8.0.5 to resolve high severity vulnerabilities

Fixes GHSA-4w7w-66w2-5vf9, GHSA-v2wj-q39q-566r, GHSA-p9ff-h696-f583.

* fix(deps): bump vite in backend lockfile to resolve audit failures

Vitest pulls in vite as a transitive dependency. Bumps to 8.0.5.

* fix(fleet): resolve version detection using package.json over stale generated constant

resolveVersion() previously returned the build-time SENCHO_VERSION
constant without checking the root package.json. When a branch fell
behind a release-please version bump, the generated constant was stale,
causing remote nodes to show "unknown" version and false "Update
available" badges. The function now walks up to the root package.json
first (authoritative source) and falls back to the generated constant
only if the walk fails.
This commit is contained in:
Anso
2026-04-06 22:05:06 -04:00
committed by GitHub
parent fb998fd0c0
commit 8ba4532995
4 changed files with 20 additions and 3 deletions
+1
View File
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
* **fleet:** fix version detection returning stale value from `generated/version.ts` instead of the authoritative `package.json`. This caused remote nodes to show "unknown" version and false "Update available" badges when both nodes were on the same version. `resolveVersion()` now reads the root `package.json` first and only falls back to the build-time constant if the walk fails.
* **fleet:** fix permanently stuck "Timed out" / "Failed" badges after node update attempts. The in-memory update tracker now supports clearing via a new DELETE endpoint, and terminal states are automatically clearable through the Recheck button.
* **fleet:** fix update completion detection for remote nodes that cannot report their version. The gateway now uses three completion signals: version change, process restart detection (`startedAt`), and offline/online detection (node went unreachable during update and came back). This eliminates false timeouts on nodes running older Sencho versions.
* **fleet:** fix 409 race condition where retrying a timed-out update was rejected because the tracker still showed "updating". The POST trigger now detects expired timeouts and allows re-triggering.
+5 -3
View File
@@ -43,9 +43,9 @@ export function isValidVersion(v: string | null | undefined): v is string {
// Resolved once per process at import time, then cached.
function resolveVersion(): string | null {
if (SENCHO_VERSION !== '0.0.0-dev') return SENCHO_VERSION;
// Fallback for manual ts-node runs without the predev hook.
// Primary: walk up to find the root package.json (always authoritative).
// The generated SENCHO_VERSION constant can be stale when a branch falls
// behind a release-please version bump, so we prefer the live value.
let dir = __dirname;
for (let i = 0; i < 5; i++) {
const candidate = path.join(dir, 'package.json');
@@ -55,6 +55,8 @@ function resolveVersion(): string | null {
} catch { /* not found, keep walking */ }
dir = path.dirname(dir);
}
// Fallback: build-time constant (may be stale in dev, but correct in Docker)
if (SENCHO_VERSION !== '0.0.0-dev') return SENCHO_VERSION;
console.warn('[CapabilityRegistry] Could not resolve Sencho version from any source');
return null;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+14
View File
@@ -389,6 +389,20 @@ docker compose pull && docker compose up -d
---
## Remote node version shows "unknown"
**Symptom:** A remote node appears online in the Fleet View but its version badge is missing or the Node Updates modal shows "unknown" in the current version column.
**Common causes:**
1. **Network issue:** The gateway cannot reach the remote node's `/api/meta` endpoint. Check that the API URL configured for the node is correct and accessible from the gateway host.
2. **Old Sencho version:** Nodes running Sencho versions older than v0.39.0 do not report a version in their metadata. Update the remote node manually to resolve this.
3. **Development build:** If running from source, ensure you have run `npm install` in the backend directory so the `predev` hook regenerates the version file.
**To verify:** Open your browser's developer tools and check the response from `GET /api/fleet/update-status`. Each node's `version` field should contain a semver string (e.g., `"0.39.4"`). If it shows `null`, the gateway could not determine the remote's version.
---
## Stack labels are not visible in the sidebar
**Symptom:** You assigned labels to stacks but no label pills appear in the sidebar.