jobizzness / jobizzness/bench

Update button refuses on untracked files, calling them "uncommitted changes"

Open
#149 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, after it had already been reviewed and its tests were green. Filed as a follow-up on the developer's call to merge #146 now.

## What is wrong

`runSelfUpdate` (`src/daemon/self-update-run.ts`) gates on the working tree being completely clean:

```ts
const statusOut = (await git(["status", "--porcelain"], root)).stdout;
if (statusOut.trim() !== "") {
return { ok: false, error: "the checkout has uncommitted changes" };
}
```

`git status --porcelain` reports **untracked** files as well as modified ones. So a single stray untracked file blocks every update, under a message that names something else.

## Evidence

Running a real update against a clone of the branch, the very first attempt refused:

```
1. status before anything: {"action":{"kind":"blocked","reason":"dirty"},"fetchError":null}
2. POST /api/update -> 400 {"error":"the checkout has uncommitted changes"}

$ git status --porcelain
?? node_modules
```

Nothing was uncommitted. The only entry was one untracked symlink. The same watcher then reported the checkout as `blocked/dirty` in the cockpit, so the button would have told the developer to go and commit work that does not exist.

This is not hypothetical on the machine Bench runs on: at 15:00 on 2026-09-17, `/var/www/bench` was carrying an untracked `src/daemon/devin-skills.ts`. With #146 live, the Update button would have refused for as long as that file sat there.

## Why tracked-only is the honest test

`git merge --ff-only` refuses on its own if an untracked file would be overwritten by the merge, so nothing is lost by allowing them. The gate exists to protect **the developer's uncommitted work**, and an untracked file is not at risk from a fast-forward that does not touch it.

## Acceptance criteria

- [ ] An untracked file that the incoming commits do not touch does not block an update.
- [ ] Modified or staged tracked files still block it, with the message they have now.
- [ ] If the fast-forward itself fails because an untracked file is in the way, that failure is reported in its own words rather than as "uncommitted changes".
- [ ] The watcher's `blocked` reason agrees with whatever the route would do — the cockpit must not say "dirty" for a checkout the route would happily update.
- [ ] A test covers the untracked case specifically: an untracked file present, update proceeds.

## Out of scope

- Stashing, or any attempt to preserve or move the developer's work. The refusal path stays a refusal.
- Changing what happens on a genuinely diverged branch.
- The build/rollback path (#146 already covers it).

## Verification

```
pnpm typecheck
pnpm test
pnpm build
```

Manual: drop an untracked file in the checkout, confirm the button still offers the update and that the update completes.

## Related

- #146 — the feature this came out of, merged as #147.

Contributor guide

Open the contributing guide

Research direction

Start in src/daemon/self-update-run.ts at runSelfUpdate and trace the watcher that reports the checkout as blocked/dirty. Run pnpm test, then add coverage for an unrelated untracked file while preserving blocking for modified or staged tracked files; verify the route and watcher agree and run pnpm typecheck, pnpm test, and pnpm build.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, typescript
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.