jobizzness / jobizzness/bench

A slow update reports failure in the cockpit while it is still succeeding

Open
#150 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.