Summary
A cold git push can take a developer machine from healthy to nearly full with no warning, because the pre-push hook launches several build-heavy jobs before anything checks free space.
lefthook.yml's pre-push runs just test-unit, just desktop-check, just desktop-typecheck, just desktop-test, just desktop-tauri-clippy && just desktop-tauri-test, and just mobile-test — in parallel, and with no disk preflight. On a cold Cargo target that is tens of GiB of build output committed to before the first byte is written.
Evidence
Measured on macOS with an empty ~/Developer/.cargo-target, pushing a branch that touched crates/** and desktop/**:
- Free space before the push: 49 GiB
- Free space ~2 minutes later, after aborting the hook mid-build: 34 GiB
~/Developer/.cargo-target at that point: 12 GiB
That is ~15 GiB consumed in under two minutes, by a hook the developer invoked implicitly by typing git push. Left to run to completion it would have consumed substantially more. Nothing in the flow warns beforehand, and the failure mode when the disk does fill is not a clean "out of space" message — it is whichever of the parallel jobs happens to die first.
This is the local-workstation counterpart to #4302 (relay wedges on ENOSPC): same underlying hazard, different side of the system.
Why a guard rather than smaller jobs
The pre-push jobs are correctly scoped — they exist to catch what CI would catch, and shrinking them trades away local signal. The problem is not their size but that their cost is invisible until it has already been paid. A preflight makes the cost explicit and refusable.
Proposed shape
A small script the build-heavy hook entries call before their real command:
- absolute thresholds rather than percentages, so behavior does not change with disk size
- reserve a fixed budget for the checks, and require a fixed floor to remain afterward
- documentation-only pushes stay unaffected (the existing globs already skip them)
- fail open on unsupported/unparseable
df output — the guard must never block a push it cannot reason about
- tunable and independently bypassable via environment variables, so a developer with a warm target can opt out of only the disk guard without skipping the other hooks
Status
I filed this after the fact: #4531 already implements the above and is open. Filing it here per CONTRIBUTING.md's issue-first guidance, so the approach can be acknowledged (or redirected) separately from reviewing the diff — the thresholds and the fail-open choice are the parts worth a maintainer's opinion.
Summary
A cold
git pushcan take a developer machine from healthy to nearly full with no warning, because the pre-push hook launches several build-heavy jobs before anything checks free space.lefthook.yml'spre-pushrunsjust test-unit,just desktop-check,just desktop-typecheck,just desktop-test,just desktop-tauri-clippy && just desktop-tauri-test, andjust mobile-test— in parallel, and with no disk preflight. On a cold Cargo target that is tens of GiB of build output committed to before the first byte is written.Evidence
Measured on macOS with an empty
~/Developer/.cargo-target, pushing a branch that touchedcrates/**anddesktop/**:~/Developer/.cargo-targetat that point: 12 GiBThat is ~15 GiB consumed in under two minutes, by a hook the developer invoked implicitly by typing
git push. Left to run to completion it would have consumed substantially more. Nothing in the flow warns beforehand, and the failure mode when the disk does fill is not a clean "out of space" message — it is whichever of the parallel jobs happens to die first.This is the local-workstation counterpart to #4302 (relay wedges on ENOSPC): same underlying hazard, different side of the system.
Why a guard rather than smaller jobs
The pre-push jobs are correctly scoped — they exist to catch what CI would catch, and shrinking them trades away local signal. The problem is not their size but that their cost is invisible until it has already been paid. A preflight makes the cost explicit and refusable.
Proposed shape
A small script the build-heavy hook entries call before their real command:
dfoutput — the guard must never block a push it cannot reason aboutStatus
I filed this after the fact: #4531 already implements the above and is open. Filing it here per CONTRIBUTING.md's issue-first guidance, so the approach can be acknowledged (or redirected) separately from reviewing the diff — the thresholds and the fail-open choice are the parts worth a maintainer's opinion.