Expand description
Where short-lived private files live:
the per-user scratch directory and the repository scratch directory,
never the system temporary directory.
What:
Where the wrapper keeps short-lived private files:
the per-user scratch directory (<state>/cli-git/scratch) for work that has no repository
in hand,
and the repository scratch directory (<common-dir>/cli-git-scratch) for work inside one.
Both are checked as private (a real directory of this user, mode 0700) and swept of entries
older than a day each time they are prepared.
Why:
The system temporary directory is never used (issue 1809, repo rule XTD):
which device backs it is unknowable (a small tmpfs, a per-user quota, a slow disk),
and a full one broke every session.
A file left by a killed process must not accumulate without bound,
so every preparation of a directory also collects what is stale in it.
doc/planning/cli-git-scratch-locations.md records the choice per site.
In TS you’d write (pseudocode):
const scratch = join(env.XDG_STATE_HOME ?? join(env.HOME, '.local/state'), 'cli-git', 'scratch');
await mkdir(scratch, { recursive: true, mode: 0o700 });
await sweepOlderThan(scratch, 24 * 60 * 60 * 1000);Constants§
- REPOSITORY_
SCRATCH_ NAME - The entry in a Git common directory that holds repository scratch.
- SCRATCH_
REFRESH_ EVERY - How often a command that waits renews its scratch directory: a twenty-fourth of
SCRATCH_STALE_AFTER, so a sweep that runs at any moment finds a waiter’s directory at most an hour old, and a directory whose waiter died stops being renewed and ages out as before. The renewal is oneutimensatcall an hour. - SCRATCH_
STALE_ AFTER - How old an entry must be before a preparation removes it:
far longer than any command of the wrapper runs, so a live process never loses a file. Measured: the longest command in the
commit timing log (1,051 commits, 2026-11-02T09:01:37Z to 2026-11-03T15:10:12Z) took 750.6 s and the longest worktree add in the
add timing log (48 adds) 539.7 s; a day is about a hundred times the longest. The patience for an owner of another namespace
(3,600 s) is far under a day. A wait for a live program’s lock has no bound of its own, so a command that waits on one for
more than a day is the case this age does not cover (none in the logs came near).
Kept as the coordinator’s pick under the owner’s delegation, open to the owner’s veto (
doc/planning/cli-git-shipped-limits-audit.md, “Sizes and ages of the service’s own housekeeping”).
Functions§
- prepare_
repository_ scratch - What: Create the repository scratch directory beside the common Git directory’s other private storage, check that it is private, sweep its stale entries, and return it. Why: Git input for a commit transaction is written there: it is on the repository’s own device, needs no environment variable, and is collected with the repository.
- prepare_
user_ scratch - What: Create the per-user scratch directory with its missing parents, check that it is private, sweep its stale entries, and return it. Why: The linter and the push probe write there; the check refuses a directory another account could write.
- renew
- What:
Sets the modification time of
pathto now. Why: A command that waits for a lock holder or for the commit service with no bound can outlastSCRATCH_STALE_AFTER, and the sweep removes entries by modification time; renewing the time while the command lives ties the entry’s age to the command’s liveness, with no lock and no record of the process. - scratch_
file - What:
An anonymous file (no name in the directory) in the per-user scratch directory of
environment, open for reading and writing. Why: Child output goes to a file so that no reader thread can outlive a stopped child; the file has no name from the start (an unnamed temporary file where the system has them, otherwise a name removed at once), so a killed process leaves nothing to collect, and the system temporary directory is never used. - state_
directory - What:
The per-user state directory of the wrapper:
cli-gitunder an absoluteXDG_STATE_HOME, or under.local/statein the home directory. Why: The injected environment decides the location, never a fixed path; a relativeXDG_STATE_HOMEis ignored as the XDG rules say. It mirrors the cache directory of the version cache. - sweep_
stale - What:
Remove every entry of
directorylast modified more thanmax_agebeforenow, and return how many it removed. A link is removed itself and never followed; a directory is removed with its contents. Why: A process killed between making a scratch file and removing it leaves the file behind, and nothing else would ever collect it. The directory belongs to the wrapper alone, so every entry in it is the wrapper’s; an entry modified in the future is kept, because a wrong clock must not delete live files. - user_
scratch_ path - What: The per-user scratch directory’s path, or the sentence that says what the environment lacks. Nothing is created. Why: Callers that run without a repository learn the location first and create it only when they need it, so a command with nothing to write never touches the disk.