Skip to content

D167 — The push force flag is cleared when a push ends, not only when one starts

Status: implemented (2026-09-05). Amends D76.

Context. FlushPush sets pushForce to make the worker skip the debounce window, and the worker clears it when it starts a push. Those two do not line up for a flush that arrives while a push is already running: the in-flight push satisfies the flush's waiter — correctly, that is what the caller asked to wait for — but it started before the flag was set, so nothing ever consumes it. The flag then survives into the next, unrelated SchedulePush, which pushes immediately instead of debouncing.

Benign in effect: the push still happens, just earlier than the window intended. It is recorded because the failure is invisible — one un-debounced push looks exactly like a correctly debounced one from the outside — and because it is the same class of bug the file's own header comment describes having already fixed once, where worker state and wake signals disagreed about what had happened.

Decision. pushForce is also cleared in the post-push critical section, alongside pushRunning and the waiter list, so the flag's lifetime is bounded by the push that satisfies the flush rather than by the push that consumes the flag.

Rationale. The alternative — having FlushPush skip setting the flag when pushRunning is already true — was rejected as racy in the direction that matters. FlushPush releases pushMu before waiting, so a push it observed as running can finish before the waiter is registered; the flush would then have neither forced anything nor been satisfied by the in-flight push, and would wait out a full debounce window it explicitly asked to skip. Clearing late is unconditional and cannot mistime.

A pending write left behind by the cleared flag is a later signal than the flush that set it, so debouncing it is the correct behavior and not a dropped force.

Consequences. The debounce window is honored for every push that is not itself forced. pushworker_test.go gains a regression test that parks the worker inside doAsyncPush by holding the git lock, which makes "the flush arrives mid-push" deterministic rather than a timing gamble.