A slow update reports failure in the cockpit while it is still succeeding
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 0
- Avg merge
- 1h 21m
- Merged PRs (30d)
- 41
Description
Found by running #146 end-to-end against a scratch clone. Filed as a follow-up on the developer's call to merge #146 now.
## What is wrong
`POST /api/update` holds the HTTP request open for the whole pull-install-build, which can be minutes. The cockpit treats any failure of that request as a failed update:
```ts
// src/client/components/useSelfUpdate.ts
try {
const res = await postJson("/api/update", {}, { local: true });
...
} catch {
setError("Could not reach the daemon to update.");
} finally {
setUpdating(false);
}
```
A client-side timeout is not a failed update. The daemon carries on, finishes, and the developer has been told it broke.
## Evidence
Driving a real update against a daemon on a scratch clone, the POST died on undici's 300-second headers timeout:
```
POST /api/update -> UND_ERR_HEADERS_TIMEOUT
```
Meanwhile, server-side, the update had already fast-forwarded and was mid-build, and it finished cleanly a minute later:
```
$ git -C log --oneline -1
1ae2872 fix(daemon): serialize self-updates and recover dist/ on rollback (#146)
$ ls -la /dist/daemon/self-update-run.js
-rw-r--r-- 1 root root 5329 16:32 # built from the pulled source
```
So the cockpit would have shown *"Could not reach the daemon to update."* over an update that worked. Tapping again then gets the `409` from the in-flight lock, which reads as a second failure.
The build in that run was slow because of a cold pnpm store, but a cold store is exactly the situation a first update after a dependency change is in.
## The shape that fits what is already there
The daemon already pushes `selfUpdate` status alongside the roster, and the button is already driven by it. The request does not need to carry the result at all:
- `POST /api/update` answers as soon as the run has *started* (202), and the pushed status carries `running`, then either the new state or the refusal.
- Or, keeping the current shape: a network failure on that POST stops being treated as a failed update, and the button waits for the pushed status to say what happened.
Either is fine; the first is less to get wrong.
## Acceptance criteria
- [ ] An update that outlives the client's HTTP timeout does not show as a failure in the cockpit.
- [ ] While one is running, the button says so — and says it from the pushed status, so a second tab and a phone agree with each other.
- [ ] A refusal (dirty tree, diverged branch, failed build) still reaches the developer with its reason.
- [ ] Reloading mid-update does not lose the fact that one is running.
- [ ] A test covers the timeout case: the POST rejects, and the button does not report failure.
## Out of scope
- The in-flight lock itself (#146 added it, it works — a second POST gets 409).
- Progress output or a build log in the UI. Just the state.
- The restart half, which already waits on the pushed status rather than its own response.
## Verification
```
pnpm typecheck
pnpm test
pnpm build
```
Manual, which a green build will not catch: an update whose build takes longer than the client timeout still ends with the button reading **Restart**, not an error.
## Related
- #146 — the feature this came out of, merged as #147.
- #149 — the other thing that end-to-end run turned up.
Contributor guide
Research direction
Start in src/client/components/useSelfUpdate.ts and inspect how POST /api/update errors and the pushed selfUpdate status drive the button. Run pnpm test first, then trace the update-start response and status handling across the client and daemon. Done means timeout-triggered updates remain in progress, refusals show their reasons, reloads retain running state, and a test covers the rejecting POST without reporting failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100