Skip to main content

Crate git_policy_cli

Crate git_policy_cli 

Source
Expand description

What: Internal native cli-git implementation, not yet the production executable. Why: The Rust rewrite preserves Git’s own argument, repository and transaction semantics.

In TS you’d write (pseudocode):

// Internal modules used by the forthcoming native entry point and consumer verification.

Modules§

action
The decision one invocation ends in: become Git, or print and exit. What: The decision one wrapper invocation ends in. Why: Forwarding replaces the process and cannot run inside a unit test; returning the decision as data lets every path be checked directly.
announced_query
The four top-level spellings after which the wrapper announces itself, and the text it prints once Git has answered. What: Recognize the four top-level spellings after which the wrapper says it is not plain Git (--version, -v, --help, -h), and build the text it prints once Git has answered. Why: A caller who types git --version should be able to tell that a policy wrapper sits in front of Git, without the wrapper changing a single byte of what Git itself printed. Every other query (--exec-path, --html-path, …) stays an exact process replacement.
auto_push
Post-commit auto-push of a landed commit: the push argv, the surfaced output and the failure reporting. What: The commit service’s push of a landed commit’s branch: which push runs, what its output shows, and how a failure is reported without failing the commit. Why: Every commit in this repository is backed up (AGENTS.md rule APG), the service pushes the branch its record names and never reads HEAD, prints only GitHub remote: lines on success and the full output on failure, and leaves a notice for the next command when the push fails. The service is the only pusher of the repositories it serves and coalesces pending pushes of one branch into one call, so the push needs no lock.
auto_push_branch_key
The per-branch file name of the push failure notice: a reversible, flat, case-safe key. What: The reversible, flat, case-insensitive-safe file name of one branch’s push files. Why: The failure notice of a push is kept per branch (push_failure_notice), and the incumbent’s per-branch push files (src/auto-push-branch-key.ts) used the same names, so a name is the same in both wrappers. Uppercase letters are escaped too, so refs that differ only in case never share a file on a case-insensitive filesystem.
auto_push_destination
Where a plain git push of one named branch goes, from the configuration and the local refs. What: Where a plain git push of one named branch would send it, decided from the configuration and the local refs, or why Git would send it nowhere. Why: The service pushes the branch a record names, which may not be the branch checked out, so it must name both sides of the push itself; naming them from a partial reading of Git’s rules pushed to places a plain git push refuses or never goes (a matching push recreating a branch the remote deleted, one of several upstream branches chosen at random, a destination that another configured refspec had already taken). Everything here follows setup_default_push_refspecs in Git’s builtin/push.c and match_push_refs in remote.c; a setting Git refuses or a form this module does not interpret becomes a skip with a note that names the setting, never a push.
auto_push_dirty_paths
The paths of a dirty worktree and of a fast-forward, and their overlap. What: The paths a dirty worktree has uncommitted work in, the paths a fast-forward would change, and the overlap between them. Why: After an auto-push publishes a merge, local main can follow it even in a worktree that holds uncommitted work, provided the move changes none of the files that work touches. Another session’s staged renames then never keep this branch behind the remote.
auto_push_reconcile
Publishing a landed commit that a moved remote branch rejected: the merge is made from objects alone and pushed. What: Publishing a landed commit when the remote branch advanced after the commit landed: the merge of the remote tip and the local tip is computed in the object store, committed without a worktree or an index, and pushed with a plain push. Why: A rejected push of a branch that the remote moved cannot succeed again, and an ordinary merge in the active worktree can be blocked, or made unsafe, by another consumer’s uncommitted files. git merge-tree --write-tree and git commit-tree read and write only objects, so the active worktree, its index and its untracked files are never touched while merging, and both histories stay reachable from the published tip. Only after the push succeeds is the branch fast-forwarded, and only where nothing can be lost: a dirty worktree follows too when the move changes none of the files its uncommitted work touches. An amended or rebased branch is merged like any other: nothing already published is lost, and the replaced commits stay in the remote history beside the new ones.
auto_push_single_flight
The real Git of a push, its query runner and the failure types of a push. What: The service’s Git for a push: the real Git, its working directory and environment, the command builder, the deadline-bound read-only query and the failure types of a push. Why: The push, its reconcile and the queries before them all start Git the same way, so none re-enters the wrapper and every Git child receives the same environment.
auto_push_trigger
The push a merge, pull, cherry-pick, revert or am hands to the service when it leaves the branch ahead of its upstream. What: Hands a push to the commit service after a command other than git commit added commits to the branch: a merge, a pull’s merge, a cherry-pick, a revert or an applied patch. Why: A clean git merge used to stay local until the next commit pushed it. The owner chose (2026-11-02) that any command adding commits queues the push as a commit does, when the branch tip moved and the branch is now ahead of its upstream and not diverged from it, and that a rebase does not, because a rebased branch would need a forced push. The check reads the branch, its upstream tracking ref and the commit parents from the files, with no Git process: the tip is read before and after the forwarded command, and the push is handed over only when the old tip and the upstream tip are both ancestors of the new tip. The old tip being an ancestor is what leaves a rebase (a rewritten branch) out, pull --rebase and pull.rebase included, without reading the configuration for it.
blob_batch
Loading many blobs through one git cat-file --batch. What: Loading many blobs through one git cat-file --batch, optionally from a private object store, with strict parsing of the batch output. Why: Post-landing completion reads the original and intended bytes of every added path; one batch replaces a Git start per object (src/policy-engine/blob-batch.ts).
bounded_push
The auto-push’s real git push with a deadline and its low-speed abort. What: Run the auto-push’s real git push with a deadline. Why: A push whose connection stalls would otherwise hold the service’s push of that branch forever, and every later push of the repository would wait behind it. The push is stopped when its deadline passes, the commit stays saved locally, and the failure is reported like any other failed push.
bounded_query
Read-only Git queries of the auto-push, each with a deadline and separate output streams. What: Run one read-only Git query with a deadline and separate output streams. Why: The auto-push asks Git a handful of questions before it pushes, and a question that never returns would hold the service’s push for good. A query that outlives its deadline is stopped and reported, so the commit stays saved.
candidate_attribute
Which candidate paths have their text Git attribute unset, read from the state the candidates were listed from. What: Which candidate paths have their text Git attribute unset (-text, which the binary macro includes), resolved by git check-attr against the same state the candidates were listed from. Why: A path whose text attribute is unset declares that its exact bytes matter, as a fuzz seed’s trailing bytes do. The attribute lives in the package’s own .gitattributes, so it moves with the package, unlike a path list kept elsewhere. Staged and projected candidates are answered from the private index, so a .gitattributes staged by the same command applies to it and an unstaged worktree edit does not; landed and pushed candidates are answered from the tree of the commit they came from.
candidate_batch
Pure framing of one git cat-file --batch reply. What: Read one git cat-file --batch reply from a byte stream, with no process involved. Why: Object bytes are untrusted input that may contain anything, including text that looks like a reply header. The reply is therefore framed only by the header’s declared size, never by searching content, and every malformed shape is a typed failure instead of a guess.
candidate_error
The one failure type of the candidate layer and its closed list of causes. What: The one failure type of the candidate layer, with a closed list of causes. Why: Every candidate failure stops policy evaluation (a file that could not be read was not checked), and callers branch on the cause without parsing message text.
candidate_known_content
Which candidates hold content that a parent commit’s tree already holds, found by exact object name. What: Which candidates hold content that a parent commit’s tree already holds, found by exact blob object name. Why: A pure rename or a copy re-lists a file as an addition, and a merge lists every file one parent lacks as an addition, although the bytes are already in history. Scanning them again refuses moves of files that were accepted when they entered history, while an object name is the hash of the bytes, so a candidate whose object a parent holds carries no byte that is new. The comparison is by object name only: never by pathname, never by similarity, so one changed byte makes the content new.
candidate_object
Validated Git object names and the file modes a candidate can carry. What: The two Git vocabulary types every candidate carries: an object name and a file mode. Why: Object names are written into the object reader’s request stream, so only text proven to be a complete hexadecimal name may ever become one; modes decide whether a candidate has blob content at all.
candidate_prediction
What git add would stage, and the worktree files a direct command selects. What: The two candidate sources that need a private index: what git add would stage, and the worktree files a direct git cli-git check or fix selects. Why: Both are answered before Git touches the real index. git add is replayed on a private copy of the index, and the candidates are the entries the replay changed, as the installed wrapper’s add-policy-facts.ts and add-staged-delta.ts define them. A direct command stages its scope with git add --all on a private copy, as direct-check-facts.ts does, and every selected entry is a candidate. Either way the bytes are blobs Git hashed from the worktree at that moment, read through the one object reader, so the number of Git processes does not grow with the number of files.
candidate_private_index
A private, timestamp-preserving copy of the real index, removed when dropped. What: A private copy of the real index, in its own directory beside it, removed when the copy is dropped. Why: Predicting what git add would stage means running it against an index nobody else reads, so the real index never changes before the policies have answered. The copy keeps the real index’s access and modification times: Git re-reads an entry whose stat data is not older than the index file, and a copy with a fresh time would make it trust stat data from the same second as a later edit of the same size, so the prediction could miss that edit (the installed wrapper’s index-file-timestamps.ts, issue #544).
candidate_reader
The long-lived object reader that answers every blob read of an invocation. What: One long-lived git cat-file --batch process that answers every object read of an invocation. Why: Starting Git costs far more than reading one object, so candidate bytes are read through a single process whose count does not grow with the number of files. Blob bytes are remembered by object name: a name always denotes the same bytes, so a remembered blob can never become stale.
candidate_record
Pure parsing of raw NUL-delimited changed-path records. What: Parse the NUL-delimited raw records of git diff-index and git diff-tree. Why: One listing process names every changed path with its mode, object and change kind, so no per-path Git process is ever needed. Pathnames are kept as the raw bytes Git printed; they are never decoded.
candidate_stage
Pure parsing of git ls-files --stage records and the delta between two index states. What: Parse the NUL-delimited stage records of git ls-files --stage -z, find the paths one staging operation changed, and turn them into candidate records. Why: git add is checked before Git runs it, by replaying it on a private copy of the index. The paths it would stage are exactly those whose index records differ between the copy before and after the replay. Comparing complete records, conflict stages included, finds a resolved conflict like any other change, and an unrelated conflict elsewhere in the index never stops the comparison. Pathnames stay the raw bytes Git printed.
candidate_store
Per-invocation listing, lazy bytes and invalidation of candidate versions. What: The per-invocation owner of candidate versions and candidate bytes. Why: Policies read committed or staged content, never whatever is in the live worktree. A version costs one listing process, its bytes come from one long-lived object reader, and neither count grows with the number of files. After a fix or replay changed the index, invalidate retires every version.
candidate_tree
The files of a commit’s tree with their modes and objects. What: Parse the NUL-delimited records of git ls-tree -r -z --full-tree <commit>: every file of one commit with its mode and object. Why: The dependent-version policy compares the candidate state with HEAD: it reads each workspace manifest’s HEAD bytes, and a direct fix may change a tracked file it did not select only when HEAD holds that file as the same ordinary blob. One listing answers both for every path. Pathnames stay the raw bytes Git printed.
candidate_version
Immutable candidate versions: identity, pathname, mode, change and object per path. What: An immutable candidate version: the paths one Git state changes, each with its identity, mode and object. Why: Every policy of one pass must judge the same files. A version is listed once and then only read, so path metadata is shared instead of re-queried, and a later fix or replay produces a new version instead of altering this one.
capture_invocation
The invocation capture: repository layout, symbolic HEAD, conclusion kind, ref backend and base. What: The transaction facts recorded before any Git mutation: the repository layout, the symbolic HEAD target, the conclusion kind, the ref storage backend, and the preparation base. Why: Every later read of HEAD inside a transaction uses the recorded base or the landed commit, never live HEAD, because another landing may have moved it since the invocation started. The layout query and the base read run as separate steps, so pruning sees the transaction before the base is read (src/policy-engine/commit-transaction-capture.ts, captureInvocationLayout and captureInvocationBase).
child_class
The classes of Git step a commit starts for itself, with the bound and the notice grace of each. What: The two kinds of Git step a commit starts for itself, and the bound and the notice grace of each. Why: One bound for every step had to be long enough for the slowest, so a stalled rev-parse held a commit for as long as a stalled pack-objects, and the first notice came after one second for steps that only needed a moment. The classes follow what a step touches, measured on the repository this crate lives in (doc/planning/cli-git-commit-timeouts.md): a query reads refs, commits and trees and ends in milliseconds whatever the worktree holds, while a step that writes objects, or that compares the index with the worktree, costs time in proportion to the bytes it reads (reset, update-index --refresh and ls-files --modified took 2.9 s on a stat-dirty 20 000-file index against 0.03 s on a clean one, and a cold disk multiplies that). The user’s own git commit with its hooks is not a class: its runtime belongs to the user and it is never bounded.
child_environment
Variables added to every real-Git child: lock PID injection and the forward-target marker. What: The environment variables cli-git adds for every real Git it starts. Why: Native Git must write lock-owner PID files, and a wrapper must be able to notice that another wrapper selected it as “real Git”.
child_watch
A watcher for waits that block on a child process: notices at the cadence and the child stopped at the bound. What: A watcher for a wait that blocks inside a read or a wait on a child process: it prints a notice after the grace and at every interval while the wait lasts, and stops the child when the wait crosses its bound. Why: The wrapper reads Git objects from a long-lived git cat-file --batch and runs Git steps of a commit with their output captured. Both block in the operating system with nothing printed and no end, so a stalled child looks the same as a slow one and holds the commit for good (issue 1811). A watcher thread sees the wait from outside: the waiting thread marks where a wait begins and ends, the watcher prints, and at the bound it ends the child so the blocked call returns.
claim_time
The clocks a claim’s lease is judged by. What: A reading of the clocks a claim’s lease is judged by: the machine and the boot the host is in, the host’s monotonic clock, and the wall clock. Two readings of one boot are compared by the monotonic clock; a reading of an earlier boot of the same machine is past at once; the wall clock judges every other pair. Why: A lease must run out when its holder stopped, not when somebody sets the date. The wall clock steps (a time sync, a person, a virtual machine resuming), and a step forward made a live holder look an hour stale while a step back kept a dead holder’s claim alive. CLOCK_MONOTONIC is one clock for every process of a host, never steps, and does not count a suspended host’s sleep, so a holder and a recoverer on one boot agree on how long a claim went unrenewed. It restarts at every boot, so a reading carries the boot ID (/proc/sys/kernel/random/boot_id). A restart ends the holder, so a claim of an earlier boot has run out at once, and a wall clock set back at boot cannot strand it. That holds only when both readings are of one machine: two hosts that share a work directory have unrelated boot IDs, and the other host’s boot is not the end of its holder. So a reading also carries the machine ID (/etc/machine-id), and a different boot ID is evidence of a restart only beside an equal, known machine ID. Equal non-empty boot IDs still compare by the monotonic clock. Where either reading has no boot ID, only the wall clock is left to compare; where either has no machine ID, a different boot ID proves no restart, so the wall clock judges that pair as the fallback. Machines cloned with one machine ID cannot be told apart; their claims are judged as one machine’s.
command_add
The Git 2.56.0 option table of git add and its bulk-staging facts. What: The complete git add option table of Git 2.56.0 and the bulk-staging facts read from it. Why: The add-explicit policy rejects staging that names no explicit path. Git accepts -A inside any cluster (-vA), by abbreviation (--al) and through --no-ignore-removal; a list of exact tokens misses those.
command_branch_create
Branch-creation facts of git branch, git checkout and git switch. What: Branch-creation facts of git branch, git checkout and git switch regions, read as Git 2.56.0 reads them. Why: The branch-worktree policy rejects creating a branch in the current worktree. It needs the explicit forms and the name Git may turn into a new local branch by matching a remote branch, for every spelling Git accepts. Gotcha: The facts read option names and argument counts only. Where Git later refuses for an option value, a combination of options, configuration or repository state, the answer stays “creates”: it errs toward the policy seeing the command.
command_branch_table
The complete Git 2.56.0 option table of git branch. What: The complete option table of git branch in Git 2.56.0. Why: Whether git branch <name> creates a branch depends on which action options were given, and whether a token is a name depends on each option’s arity. Rows follow options[] in cmd_branch (builtin/branch.c:992-1050) in source order.
command_checkout_table
The complete Git 2.56.0 option tables of git checkout and git switch. What: The complete option tables of git checkout and git switch in Git 2.56.0. Why: Both commands create branches explicitly (-b, -c, --orphan, --track) and implicitly (a name that matches one remote branch). The tables are assembled as Git assembles them: the command’s own rows, then the shared groups (builtin/checkout.c:1786-1847, 2118-2129, 2170-2179).
command_clean
The Git 2.56.0 option table of git clean and its deletion facts. What: The complete git clean option table of Git 2.56.0 and the deletion facts read from it. Why: The linked-worktree policy guards a clean that can delete files. Git’s last --dry-run/--no-dry-run wins, in clusters (-ndX) and abbreviations (--dry).
command_commit
Facts about the arguments after git commit. What: Facts about the arguments after git commit, read as Git 2.56.0 reads them. Why: The commit-only transform, the dry-run check and the commit transaction all decide from these facts. Every fact is the final state after Git applied each option in order, so --all --no-all reports all off, exactly as git commit would act.
command_commit_table
The complete Git 2.56.0 option table of git commit. What: The complete option table of git commit in Git 2.56.0. Why: Declaring every option Git accepts removes the guess about whether an undeclared option consumes the next token. Rows follow builtin_commit_options (builtin/commit.c:1705-1785) in source order, which the completion control compares.
command_config
The form and file scope of a git config region. What: The form and file scope of a git config region, read as Git 2.56.0 reads it. Why: The require-root policy exempts git config when it works on the per-user or system file, or lists. Whether --global is that option or a value being stored depends on the form: git config user.name --global writes the text --global into the repository’s own configuration.
command_config_table
The Git 2.56.0 option groups of git config. What: The option groups of git config in Git 2.56.0, one constant per group. Why: Git builds each git config subcommand’s table from shared groups (CONFIG_LOCATION_OPTIONS, CONFIG_TYPE_OPTIONS, CONFIG_DISPLAY_OPTIONS, builtin/config.c:67-117) plus a few rows of its own. Keeping the same groups keeps one copy of every row.
command_conflict_side
The checkout and restore command lines that take one side of a conflicted file. What: Which git checkout and git restore command lines take one side of a conflicted file, and the paths they name. Why: During a merge, a rebase, a cherry-pick or a revert the usual way to settle a conflict is git checkout --ours|--theirs -- <path>, git checkout <rev> -- <path> or the git restore forms of the same. The linked-worktree policy rejects them as discarding forms; the conflict-side guard lets them through for conflicted paths after saving the file. Only a command line whose every token is understood qualifies: a form with --force, --patch, --pathspec-from-file, a branch option, an abbreviated git restore option, an index-only restore, or no -- before the paths stays rejected, because what it would discard is not known from the arguments alone.
command_discard
The commands that can overwrite or delete uncommitted state, read as Git reads them. What: Which regions of git checkout, git switch, git restore, git rm, git mv, git checkout-index, git read-tree and the --abort forms of merge, rebase, cherry-pick, revert and am can overwrite or delete uncommitted state. Why: The linked-worktree policy confines such commands: it rejects them in the main worktree and, once the worktree holds uncommitted state, in a linked worktree too. Each region is read as Git 2.56.0 reads it, so a spelling Git accepts as --force is not missed and a value that looks like a flag is not mistaken for one. Gotcha: Where Git itself refuses the region the answer is the refusal; the caller lets Git say so.
command_options
Git 2.56.0 parse-options tokenization of one command’s argument region. What: Read one Git command’s arguments the way Git 2.56.0 parse-options.c reads them. Why: A policy that guesses which token is an option, a value or a path can be bypassed by a spelling Git accepts. Every per-command module declares Git’s own option table and this module walks the arguments exactly once with Git’s rules.
command_options_query
Final-state and position questions over a tokenized region. What: Small read-only questions over a tokenized Git argument region. Why: Git applies options in order and the last use wins, so command modules ask for final states and positions instead of counting spellings.
command_push
The Git 2.56.0 option table of git push and its facts. What: The complete git push option table of Git 2.56.0 and the facts read from it. Why: The atomic-push transform must know whether the caller already chose atomicity, and the manual-push gate whether the push is a dry run; both in every spelling Git accepts (--at, --no-at, -nf), which a list of exact tokens misses.
command_reset
The Git 2.56.0 option table of git reset and its mode. What: The complete git reset option table of Git 2.56.0 and the mode read from it. Why: The linked-worktree policy guards resets that rewrite worktree files: --hard, --merge and --keep. The five mode options write one variable, so only the last one counts, in any abbreviation Git accepts.
command_stash
git stash subcommand dispatch and wrapper-flag positions. What: Which git stash subcommand a region runs in Git 2.56.0, and where wrapper-only flags sit in it. Why: The linked-worktree policy guards stash; its escape hatch must be found in option position of the subcommand that actually reads the arguments, so a message or a path that spells the hatch is forwarded untouched.
command_stash_table
The Git 2.56.0 option tables of every git stash subcommand. What: The option tables of every git stash subcommand in Git 2.56.0. Why: git stash dispatches to subcommands that each parse their own options with their own flags (builtin/stash.c); whether a token is an option value depends on which subcommand reads it.
command_status
The Git 2.56.0 option table of git status, its facts and the advice-key reading. What: The complete git status option table of Git 2.56.0, the machine-readable fact read from it, and the reading of advice.statusHints set before the subcommand. Why: The status-hints transform must not override an explicit caller choice, and the wrapper adds no note to output a program will parse.
command_worktree
Whether a git worktree invocation creates or moves a worktree. What: The one fact the wrapper needs about git worktree: whether the invocation creates or moves a worktree. Why: Only git worktree add and git worktree move are sources of ignored-state synchronization, and the worktree-copy opt-out is written after that second word.
commit_args
Argument transforms of native preparation: normalisation, global split, -a removal and private arguments. What: Argument transforms of native preparation: the normalised commit argument region, the split of global options from the commit region, the removal of -a/--all, and the arguments of the real git commit that run against the complete private index. Why: Native git commit must see the policy-settled index, not worktree bytes again, so selection controls and pathspecs are removed once the private index already holds them (src/policy-engine/commit-preparation-native.ts, commit-transaction-selection.ts, src/parser/commit-normalise.ts). The removals match the incumbent token for token, including its use of lastIndexOf, so a -m value equal to a pathspec is removed from the same position.
commit_command
The git commit command: the transaction, the settled events, the post-commit gate and the backup. What: The git commit command of the native wrapper: the preparation and landing through the resident service, the settled events, the post-commit gate and the handoff of the automatic backup. Why: The wrapper runs the commit instead of forwarding it, prints the pre-forward events only after the commit returned, then runs the post-commit gate for a landed commit and, when the gate passes, hands the push to the service. A commit that landed nothing exits with its own status.
commit_conclusion
The conclusion of a settled commit transaction: preparation, landing and post-landing completion. What: The events of a settled commit with the fix summary the policies’ corrections earned. Why: The summary appears only after some pass changed candidate bytes, and a normalization that removed every change always reports it. The conclusion that once landed the commit itself (hook dispatcher, native preparation in the shadow, the landing loop) is gone: the resident service is the only writer of a commit.
commit_convergence
Slice 6: the commit path’s bounded policy convergence over the private index. What: The commit path’s bounded policy convergence over the private commit index: each changed pass writes its corrections into the index, then the whole ordered pass restarts until it proposes nothing. Why: The commit path must end with the corrections the policies settled on, and must stop when they repeat a state or do not settle. The loop is the shared converge; this module supplies the commit path’s three operations, as the direct fix does for the worktree. Exact prior states are compared in memory; the incumbent also streams them to snapshot files, which only serve the same comparison.
commit_facts
Rule facts from the real repository with candidates read from the private index. What: The repository facts of a commit transaction: every rule fact is read from the real repository, and the candidate files are read from the private index and the shadow object store against the recorded base. Why: The incumbent’s rules (commit-only, commit-index, commit-sequencer) ask real Git about the real index and the real sequencer state, while the content policies read the private commit index that the transaction corrects. Pointing every fact at the private index would answer the rules about a state the user never staged.
commit_index
The private commit index: copies, selection modes, pathspec files and listings. What: The private commit index of one transaction: its timestamp-preserving copies of the real index, its initialization for each selection mode, the intended tree written from it, its path listings, the pathspec-file source, and interactive and patch selection run against it. Why: Git’s staging and selection run against a private index and the shadow object store, so the real index and object store change only at landing. A copy keeps the real index’s timestamps, because Git re-reads a stat entry whose time is not older than the index file (index-file-timestamps.ts, issue #544).
commit_no_change
The normalization-only transaction for a correction that removed every change. What: The normalization-only commit: a correction whose complete intended tree equals the recorded base ends without an empty commit, and says so. Why: The preparation (service_prepare.rs) decides it before anything is sent to the service; this module holds the finding’s identity and its message, so the words of the finding stay in one place (src/policy-engine/commit-transaction-no-change.ts).
commit_preparation
Native git commit in the shadow, the prepared commit read back and prepared.json. What: Native git commit in the shadow repository against the private commit index, the prepared commit read back from the shadow, and prepared.json. Why: Git owns the hooks, the editor, the templates, the message cleanup and the signing; this module only points Git at the shadow and the private index. Every hook is off in the shadow (its config switches the commit events off and the hooks-off option comes first), so Git starts no external program for a hook and never starts the wrapper. A failing preparation removes the shadow so no shadow outlives the failure (src/policy-engine/commit-preparation-native.ts, src/shadow-repository/shadow-repository.ts).
commit_staged_additions
The finding for a staged deletion that an explicit-path commit cannot record. What: The staged additions an explicit-path commit must carry into its private index, and the pathspec-file reader that tells which selection they belong to. Why: A path staged as an addition in the real index is tracked there, so native git commit -- <path> takes its worktree content without asking any ignore rule. The private index of an explicit-path commit starts from the recorded base, where such a path is untracked, so git add applies the ignore rules: a directory pathspec silently matches nothing, and a file pathspec fails with a hint to force-add that commit does not accept. Carrying the real index’s entry first makes the path tracked in the private index, and git add then refreshes it from the worktree exactly as native Git does.
commit_staged_deletions
What: The commit-only finding for a staged deletion that an explicit-path commit cannot record because the path is still on disk. Why: An explicit pathspec selects worktree content, never index state, so a deletion staged with git rm --cached for a path that stays on disk (an ignored editor file, for example) is not the content the pathspec selects. Without this finding Git answers “nothing to commit”, or a private git add fails with a hint to force-add the ignored path, which would re-track the file instead of recording its removal.
commit_timing
The record of where one wrapped commit spent its time, appended beside the discard audit. What: The record of where one wrapped git commit spent its time, appended to a size-capped file in the common Git directory beside the discard audit: preparation, building, setting up the service, reaching it, waiting for its answer, the work after landing, the post-commit gate and the hand-over of the push. Why: A commit that took seventeen seconds could not be split afterwards, because nothing outside verbose mode said which phase held the time. The numbers are taken from the monotonic clock as the commit runs (one Instant read per phase edge, no Git start, no flush to disk) and written once, in one append, when the command ends. The record goes to the file only: the person’s terminal never shows it.
commit_transaction
The commit transaction in the incumbent’s order, up to the settled policies. What: The values every commit of the service route shares: the invocation, how it can stop, what it came to, the selection the command line makes, and the policy checks over the private index. Why: The preparation (service_prepare.rs) and the commit command (commit_command.rs) pass these between them. The transaction that once landed a commit itself, with its registry, recovery and replay, is gone: the resident service is the only writer of a commit (doc/planning/cli-git-resident-service.md, “Landing every command line”).
commit_worktree_records
The worktree completion records of selected corrections and of hook changes. What: The worktree completion records a landed commit owes the worktree: selected final-newline corrections. Why: The commit keeps what the private index holds, so the real worktree still holds the bytes the user wrote. After the landing, each recorded file takes the committed bytes only while it still holds its earlier bytes (src/policy-engine/commit-transaction-selected-worktree.ts).
conclusion_cleanup
Native conclusion cleanup reproduced in the owning worktree. What: Native conclusion cleanup reproduced in the owning worktree’s Git directory after a merge, cherry-pick or revert conclusion lands, and the names of that state. Why: Native git commit removes MERGE_HEAD and its siblings in the shadow; the real worktree must end the same way, but an entry is removed only while it still holds the bytes or value copied at preparation, because a changed entry belongs to a later command (src/shadow-repository/shadow-conclusion-cleanup.ts, -files.ts, -names.ts).
conclusion_copy
The shadow conclusion copies at preparation. What: The merge, cherry-pick and revert conclusion state copied into the shadow at preparation, and into the transaction’s comparison copies. Why: Native git commit in the shadow reads the conclusion state the real worktree holds at invocation. Landing later compares the real entries with these copies, so an entry changed by another command after invocation is kept (conclusion_cleanup.rs holds the landing side). Pseudorefs of a reftable repository live in the ref store and are copied with update-ref (src/shadow-repository/shadow-conclusion-state.ts).
config_discarded
The discardedCopies section of cli-git.config.jsonc. What: The discardedCopies section of cli-git.config.jsonc: how long and how much the folder of saved copies of conflicted files may keep (maxAgeDays, maxSize). Why: The settings of the wrapper live in one file with one validation, so a limit is a key there and not a second source in Git configuration. A value that is no limit (zero, negative, a fraction, text that is not a size) is a configuration failure that names the key, like every other bad value, and so is never read as “no limit” or as a zero that would remove every copy. Pruning itself is discarded_prune.rs.
config_error
The one failure type for rejected configuration content or files. What: The one failure type for rejected cli-git.config.jsonc content or files. Why: Callers render a single configuration diagnostic without parsing message text.
config_file
Repository-root discovery and bounded reading of cli-git.config.jsonc. What: Find and read cli-git.config.jsonc at a repository root. Why: Configuration is one data file at one fixed place; legacy executable configuration is reported for migration and never read, run or silently ignored.
config_loading
Lazy configuration loading for known inspection versus mutation/ambiguous commands. What: Policy-configuration loading decisions before expensive policy initialization. Why: Known inspection forms keep the fast path; mutations, aliases and ambiguous options require configuration.
config_parse
Whole-document JSONC validation into typed settings. What: Turn cli-git.config.jsonc text into validated typed settings. Why: Every unknown key, wrong type or ambiguous duplicate is rejected before any policy or transaction reads a setting.
config_rules_file
The rulesFile option: a repository-relative name that stays inside the repository. What: The rulesFile option of security/forbidden-strings: which files in the repository hold the scanner’s private rules, each written relative to the repository’s top level. This module checks one path; config_policies.rs reads the string and array forms and calls it for each path. Why: The user decided on 2026-10-05 that configuration names the rules file first, then FORBIDDEN_STRINGS_RULES, then the default file. A configured name is checked when the configuration is read: it must stay inside the repository by its words alone, so a configuration cannot point the scanner at a file elsewhere on the machine. A name that is missing on disk is not a configuration error; loading the rules reports it when the policy runs.
config_schema
Typed settings and their defaults. What: The typed settings a validated cli-git.config.jsonc produces, with defaults. Why: The policy engine and transactions read plain typed values, never JSON.
config_snapshot
One git config --list read, parsed, and the lookups the commit path makes against it. What: One read of the whole Git configuration (git config --list -z --show-scope), parsed here, and the lookups the commit path makes against it instead of starting git config for each key. Why: Every git config start costs a process, and a wrapped commit asked Git the same configuration several times (core.hooksPath, the hook switches, the maintenance keys, the ref storage). Git lists entries in precedence order (system, global, local, worktree, then GIT_CONFIG_COUNT, GIT_CONFIG_PARAMETERS and -c values, all in the command scope), so the last entry of a key is the value git config --get prints. A lookup answers only when it can reproduce Git’s own answer exactly; any value it does not model (~ and %(prefix) paths, unit suffixes, hexadecimal, a bad boolean) is Lookup::Unhandled, and the caller starts Git for that key as before.
conflict_side_guard
The linked-worktree policy’s carve-out for conflicted files during a merge, rebase, cherry-pick or revert. What: The linked-worktree policy’s carve-out for conflicted files: judge a git checkout or git restore command line during a merge, rebase, cherry-pick or revert, and save the files it would replace. Why: The owner decided (2026-11-02, “save, then allow”) that taking a side of a conflicted file is allowed once the current file is copied beside the audit log, and that every path that is not conflicted keeps the rejection. This file joins the pieces that decide and save (conflict_sides.rs, command_conflict_side.rs) to the repository: it finds the layout and the index in process, starts no Git, and returns what the policy should do. The copy is made here, before the policy lets the command run, so Git never starts before the copy is on disk.
conflict_sides
Conflicted paths, the decision on a command line that names them, and the saved copy of each file. What: The decision and the file work behind taking one side of a conflicted file: which operation is in progress, which paths the index marks as conflicted, whether a command line names only such paths, and the saved copy of each file before the command replaces it. Why: linked-worktree-only rejects git checkout --theirs -- a.rs because it overwrites the worktree file. During a merge, a rebase, a cherry-pick or a revert that command is how a conflict is settled, and the file it overwrites is the half-resolved copy a person may have edited by hand. The owner’s decision is to let it through for conflicted paths after the current file is copied beside the audit log (discard_audit.rs), so nothing is lost and the copy is named. Everything here is read in process: the operation from the state files in the Git directory, the conflicted paths from the unmerged entries of the index file. The copy is written and synced before the caller lets Git run.
dependent_version_bundled
Which development dependencies each dependent bundles. What: Decide which development dependencies each dependent bundles, reading source only where an edge could carry a raised version. Why: A development dependency ships inside a dependent only when its non-test source imports it. Scanning every package’s source on every commit would read the whole workspace, so, as in the incumbent (dependent-bump-workflow.ts:176-278), only dependents that could reach a raised package if every development dependency were bundled are scanned, and only for edges that lead toward a raised package.
dependent_version_content
The repository content the dependent-version planner reads, and its two failure kinds. What: The repository content the dependent-version planner reads, behind one small interface, and the two ways planning can fail to finish. Why: The planner is pure with respect to the repository: it never starts Git and never reads the filesystem. The policy’s wiring answers from the wrapper’s candidate layer, the release path from the worktree, and tests from memory. Failures are reported by cause: content that could not be read is content-unavailable; content that was read but that the planner cannot use is policy-incomplete.
dependent_version_graph
The dependency walk and the plan of dependent bumps. What: Walk the workspace dependency graph from the packages whose version was raised and plan a patch bump for every publishable package that reaches one of them. Why: A dependent’s published contents change when a dependency it ships with changes, so its version must move too. This is the incumbent’s closure and filter (dependent-version-bump.ts:183-350): edges to names outside the workspace and self edges are ignored, the walk passes through unpublished packages and cycles, and only publishable, versioned, not-yet-raised packages are bumped.
dependent_version_imports
Whether source text imports a package. What: Decide whether source text imports a package or one of its subpaths. Why: A development dependency is bundled, and so carries a bump, only when non-test source imports it. This is the incumbent’s specifier scan (source-imports.ts:59-264): a quoted specifier equal to the name, or starting with the name and /, directly after from, import, import( or require(. Every character it inspects is ASCII, so scanning UTF-8 bytes decides exactly what scanning UTF-16 units decides.
dependent_version_lifecycle
The dependent-version planner’s view of a prepared candidate state. What: The dependent-version planner’s view of a prepared candidate state: every tracked file of the private index, its bytes with this invocation’s corrections laid over them, and the HEAD commit’s copy of a file. Why: The planner reads workspace manifests, the registry configuration and source files wherever they are, not only the candidates; the installed wrapper’s policy reads them as tracked files of the candidate state (trackedFiles, bytes, headBytes). Everything goes through the store the candidates were prepared in, so a direct fix’s later pass reads what its earlier passes corrected.
dependent_version_manifest
Strict JSON reading of a manifest’s name, version and dependency names. What: Read the facts a dependent bump needs from one package.json: its name, its version, and the workspace names it depends on. Why: The incumbent parses manifests with JSON.parse (manifest-text.ts:130-161). This module accepts exactly JSON: it parses with the crate’s JSONC parser and first refuses the two things JSONC adds, comments and trailing commas. Keys follow JSON.parse: the last duplicate wins. A manifest is decoded as strict UTF-8 after one optional byte-order mark, as the incumbent’s fatal TextDecoder does.
dependent_version_paths
Which paths the dependent-version planner reads: manifests, configuration, source. What: Which repository paths the dependent-version planner reads: workspace manifests, the registry configuration, and a package’s non-test source files. Why: The planner selects these from the tracked-path list itself, so the provider needs no pathspec support. The rules are the incumbent’s: isWorkspaceManifestPath (dependent-version-bump-policy.ts:66-73), the manifest pathspec :(glob)package/*/*/package.json, PNPR_CONFIG_PATH (publishable-names.ts:10) and isNonTestSourcePath (source-imports.ts:38-55).
dependent_version_plan
The dependent-version plan with exact manifest bytes before and after. What: Plan patch bumps for publishable dependents of packages whose version differs from the base, with the exact manifest bytes before and after each bump. Why: This is the one planner behind both entry points: the commit-time policy and the release workflow’s ripple, which will call the native wrapper’s direct fix. It is the incumbent’s planWorkspaceBumps (dependent-bump-workflow.ts:280-388) over the content seam: it starts no Git process and reads no file.
dependent_version_policy
The decision and findings of mono/dependent-version-bump. What: The decision of mono/dependent-version-bump: when to plan, and the findings a plan becomes. Why: The plan is a pre-commit step. Another forwarded command such as git add could not apply its patches, so it plans only for git commit among forwarded commands, and for git cli-git check and git cli-git fix; and only when a workspace manifest is modified, so ordinary commits read nothing. Each stale dependent is one finding whose patch rewrites a tracked manifest that may not be a candidate, which adds that manifest to the commit (dependent-version-bump-policy.ts:178-235).
dependent_version_publishable
Publishable package names from the generated registry configuration. What: Read the publishable package names from the generated registry configuration (package/config/pnpr/config.yaml). Why: Only published packages receive a dependent bump. The generator writes each name as a single-quoted YAML list item on its own line under packages:, so a line scan reads exactly what it wrote, as the incumbent does (publishable-names.ts:38-81). Lines are trimmed with JavaScript’s String.prototype.trim set of characters.
dependent_version_release
Patch bumps of plain release versions and JSON.stringify quoting. What: Patch-bump a plain major.minor.patch release version, and quote text the way JSON.stringify does. Why: A dependent receives a patch bump. Only a plain release can be bumped automatically; anything else (a prerelease, build metadata, a leading zero, a missing component) is reported for a bump by hand, with the incumbent’s message (dependent-version-bump.ts:87-177). The patch component is incremented as a decimal digit string, so it is exact at any size.
dependent_version_states
Every workspace manifest at both states, and which versions were raised. What: Read every workspace manifest at the candidate state and at the base, and decide which packages’ versions were raised. Why: The plan starts from these facts. The order of failures follows the incumbent’s: every manifest is decoded before any is parsed (its reader decodes while listing, dependent-version-bump-policy.ts:82-92), and each manifest’s base text is parsed before its current text (dependent-bump-workflow.ts:150-174).
dependent_version_text
The byte-preserving rewrite of a manifest’s top-level version. What: Rewrite the top-level version value in a manifest’s text, leaving every other byte unchanged. Why: The bump is delivered as a full-content patch, so indentation, key order, line endings and the trailing newline must survive exactly. This is the incumbent’s scanner (manifest-text.ts:184-333) over bytes: it tracks {/[ depth outside strings, finds the first "version" key at depth one, and requires its raw value to be exactly the JSON quoting of the expected version.
diagnostic_log
Line-oriented debug and warning diagnostics on standard error. What: Line-oriented debug and warning diagnostics on standard error, switched like the incumbent’s console sink. Why: The incumbent prints debug records only when MONOCHROMATIC_VERBOSE=true and warnings unless MONOCHROMATIC_WARN=false (package/logger/core/src/sink/console.ts), and its spec requires that diagnostics never corrupt a JSONL stream: each diagnostic is one complete line written in one call (SPEC.md, “Debug logging”).
diagnostics
JSONL event rendering for wrapper-made diagnostics. What: Render wrapper diagnostics as the JSONL events callers already parse. Why: Agents and hooks read cli-git’s event stream line by line; every wrapper-made event must be one compact JSON object per line with stable field order.
direct_fix
The direct fix: converge corrections in memory, then install them. What: git cli-git fix: run policy passes over the selected files, apply the corrections they propose in memory until a pass proposes none, then install the corrected files into the worktree when that last pass is clean. Why: The installed wrapper’s direct-fix.ts and direct-fix-convergence.ts decide the behaviour: only the last pass is reported, a fix summary follows it only when a file changed, nothing reaches the worktree unless the last pass exits 0, and a fix that cycles or keeps changing ends with one engine failure. The bounded loop itself is policy_convergence::converge; this module supplies its passes. Corrections replace whole file contents in memory instead of being staged into the private index, so a pass starts no Git process for them.
direct_fix_install
Installing a direct fix’s corrections into the worktree, all or none. What: Install the corrections a converged git cli-git fix settled on into the worktree, all of them or none, and never touch the index. Why: The installed wrapper’s direct-fix-install.ts decides this protocol: every corrected file must still hold the bytes the fix read, each replacement is written beside its file and renamed over it, a copy of the old file is kept until the end, and the real index must hold the same bytes afterwards. Any failure restores every file already replaced. Failure messages name no file, because a pathname can hold the text a scan exists to keep out of output.
discard_audit
The append-only record of every command form that can discard uncommitted state. What: The append-only record of every command form that can discard uncommitted state: rejected, allowed because the worktree was clean, exempt, or run with the escape hatch. Why: When uncommitted work vanishes, nothing said which command did it, and the person who lost it blamed the wrong one. One line per decision, with the time, process, parent process, directory and arguments, makes a loss attributable. Writing the record never changes the outcome of the command.
discarded_prune
The bound on the folder of saved copies of conflicted files: age, size, audit and doctor report. What: The bound on <common git dir>/cli-git/discarded/: copies older than a number of days are removed, and the folder is kept under a size by removing the oldest copies first, never one younger than a day and never the copy just written. Each removed copy leaves one line in the audit file, and git cli-git doctor reports the folder. Why: The copies of conflicted files were never removed, so the folder grew for as long as the repository lived. They are the only remaining content of a file a command replaced, so the bound errs toward keeping: both limits are settings of cli-git.config.jsonc (discardedCopies.maxAgeDays, discardedCopies.maxSize, read by config_discarded.rs), a copy younger than a day is out of reach of both, and the pass runs only after a new copy is complete and durable (so a kill while copying costs the older copies nothing), takes no lock, and touches only directories named like the wrapper’s own (2026-11-02T14-01-07, with an optional -<number>).
doctor
git cli-git doctor: the traditional hooks a repository holds, which are unsupported. What: git cli-git doctor: list the traditional hooks a repository holds and the external hooks its Git configuration defines, and note the retired hooks.concurrentCommits key. Why: Traditional hooks (executable scripts in <git-common-dir>/hooks or in a core.hooksPath directory) are unsupported, and their behavior with the wrapper is undefined. The commit path warns when core.hooksPath is configured but never scans a directory, because a scan costs file system calls on every commit; this command is where the scan happens, on request. Exit status 0 means none were found, 1 means at least one was (a configured hook counts), and 2 means the repository could not be located. A hook file that is byte for byte the shim git lfs writes for its own name is expected and harmless, because the wrapper does the Git LFS upload itself and starts Git with traditional hooks off; it is explained apart from the other hooks and does not make the exit status 1.
doctor_config
What git cli-git doctor reports from the configuration: configured hooks and the retired hooks key. What: What git cli-git doctor reports from the configuration: the external hooks Git configuration defines, and the retired hooks.concurrentCommits, indexLock.unprovenOwnerTimeoutMs and landing.reserveAfterLostRaces keys. Why: The wrapper runs no external hook, so a configured hook is a finding the author must act on, and each retired key in cli-git.config.jsonc is accepted for older files but means nothing; doctor says both plainly. The commit path prints the first as a warning from the snapshot it already holds; doctor lists it on request.
doctor_filesystem
The filesystem section of git cli-git doctor: btrfs subvolume, overlayfs, the removal mount option and its commands. What: The filesystem section of git cli-git doctor: what the repository’s main checkout sits on (btrfs subvolume, btrfs directory, overlayfs, other), whether the mount allows a regular user to delete a subvolume, and the exact commands that change what can be changed. Why: Cheap worktrees need a btrfs subvolume, and deleting a worktree that is a subvolume in one step needs the mount option user_subvol_rm_allowed. Neither is visible to the person until something is slow, so the doctor says what holds and prints the command, never runs it. Everything is informational: the section goes to standard error after the hook report and never changes the exit status. It reads statfs, one inode number, /proc/self/mountinfo, /run/ostree-booted and /proc/cmdline, and starts nothing. The facts are gathered apart from the wording (FilesystemFacts in, text out), so the wording is tested on built facts and the exact-output controls of the doctor and the management command stay independent of the host they run on.
effective_target
Worktree target classification for worktree-enforcing policies. What: Classify the worktree an invocation targets for worktree-enforcing policies. Why: Linked-worktree safeguards discipline a person’s repositories, not the disposable clones a tool keeps in its own cache.
empty_tree
The empty tree’s object name under each known object format. What: The object name of the empty tree under each object format Git supports, known without asking Git. Why: The invocation capture needs the empty tree as the baseline of an unborn branch. Git hashes the framed bytes tree 0 followed by a NUL byte, so the name depends on the object format alone and never on the repository’s contents. Spawning git hash-object -t tree --stdin for a constant cost a process on every commit.
entry
The per-invocation decision the thin executable performs. What: Decide what one git invocation of the native wrapper does, and perform it. Why: The executable stays thin: it gathers process facts, asks this module for an action, and performs it. The decision itself is testable without replacing the test process.
escape_hatch
Spellings of the wrapper-only escape hatches shared by several modules. What: The spellings of the wrapper-only escape hatches several modules share. Why: A command module puts its own hatch first in the flag list it hands the option tokenizer, and the control table lists the same spellings; one constant per spelling keeps both in step. Removal is not done here: wrapper_invocation.rs deletes wrapper tokens by the positions the tokenizer reports.
event_hints
The intent-guessing hint that follows a finding, an engine failure or a notice. What: The hint that follows a finding, an engine failure or a warn-unsafe notice: a guess at what the person was trying to do, and the concrete next step. Why: A block that only states a rule leaves the next move to guesswork. Every block and every divergence from real Git’s behavior names the likely intent and a way forward, so a rejected command is a step toward the goal. Messages built with their own hint (the discard, branch, add, root and commit-only rejections) are listed as self-hinted, and a guard test fails when a code has neither.
event_path
A pathname as events report it: text, plus base64 pathBytes when it is not UTF-8. What: A repository path as an event reports it: readable text, and the exact bytes as well when those bytes are not UTF-8. Why: Git allows any bytes in a file name, and a JSON string can hold only text. An event keeps path as text with replacement characters, so every existing reader works unchanged, and adds an optional pathBytes field, base64 encoded, only for a name that is not UTF-8, so a reader that needs the file can still find it. This is option C of doc/planning/cli-git-rust-open-decisions.md, section “Non-UTF-8 paths in events and journals”, which the owner adopted.
external_hooks
The configured external hooks, read from the snapshot, and the loud report that none runs. What: The external hooks a repository configures (hook.<name>.event with hook.<name>.command), read from the configuration snapshot, and the loud report that none of them runs. Why: The wrapper supports no external hook program at all: every real-Git start of the wrapper switches every hook event off (hooks_off.rs), and the behaviors a hook used to provide are declared natively in cli-git.config.jsonc. A configured hook that silently never ran would look like a broken check, so a commit prints one configuration-warning event naming the keys and the events, decided from the snapshot the commit already holds (no process start), and git cli-git doctor lists the same hooks.
forwarded_command
The Git command a forwarded invocation runs, after ordinary alias expansion. What: The Git command a forwarded invocation actually runs, after ordinary alias expansion. Why: Git expands an alias only when no built-in has that name, reads it from alias.<name> or alias.<name>.command, splits it with split_cmdline quoting, lets its leading words be global options, and expands again when the result is an alias. Index-writer coordination and the commit rules decide from the expanded command, so git -c alias.c=commit c is a commit (src/forwarded-command.ts).
forwarding
Starting real Git with unchanged arguments, streams and exit status. What: Start real Git with the caller’s exact arguments, streams and environment. Why: The wrapper adds policy around Git; it must never change what Git receives or what the caller observes from Git.
fs_btrfs_ioctl
The btrfs ioctls that create, snapshot and destroy a subvolume, called in process. What: The three btrfs ioctls the wrapper needs, called in process: create an empty subvolume, snapshot a subvolume, and destroy a subvolume. Why: libc has FICLONE and the filesystem magic numbers but no BTRFS_IOC_* constants, and the wrapper never starts the btrfs program (the repository’s rule: embed instead of spawning). The request numbers and the 4096-byte argument structures are taken from the kernel header include/uapi/linux/btrfs.h (the host copy is /usr/include/linux/btrfs.h): BTRFS_IOCTL_MAGIC is 0x94; BTRFS_IOC_SUBVOL_CREATE is _IOW(0x94, 14, struct btrfs_ioctl_vol_args); BTRFS_IOC_SNAP_DESTROY is _IOW(0x94, 15, struct btrfs_ioctl_vol_args); BTRFS_IOC_SNAP_CREATE_V2 is _IOW(0x94, 23, struct btrfs_ioctl_vol_args_v2). Both structures are 4096 bytes: vol_args is an s64 fd and 4088 name bytes; vol_args_v2 is an s64 fd, u64 transid, u64 flags, a 32-byte union and 4040 name bytes. strace of btrfs subvolume snapshot and btrfs subvolume delete shows the same shapes (doc/planning/cli-git-btrfs-worktree.md, “Facts”). Each call is proven by one real call in a test on a btrfs fixture, and the encoding of the request numbers is proven against libc::FICLONE. Permissions (kernel fs/btrfs/ioctl.c): creating a subvolume is unrestricted; a snapshot needs ownership of the source; destroying one needs the user_subvol_rm_allowed mount option for an unprivileged user.
fs_clone_exchange
FICLONE of one file into another and the atomic RENAME_EXCHANGE of two paths. What: Two more Linux primitives: clone a whole file’s data blocks into another file (FICLONE), and swap two paths atomically (renameat2 with RENAME_EXCHANGE). Why: A reflink copy shares the data blocks of a file instead of copying them (btrfs, XFS with reflink, and files inside one overlayfs whose layers share such a filesystem), and an exchange swaps a directory with its replacement in one step, so no process ever sees the path missing. Both fail with a typed error the caller falls through on: FICLONE answers EXDEV across filesystems, EOPNOTSUPP or EINVAL where the filesystem has no reflinks; the exchange answers EINVAL or EXDEV where the filesystem cannot swap. The ladder of doc/planning/cli-git-btrfs-worktree.md uses the clone as its second rung.
fs_kind
Which filesystem holds a path (btrfs, overlayfs, other) and whether a directory is a btrfs subvolume root. What: Which kind of filesystem holds a path (btrfs, overlayfs, other), whether a directory is the root of a btrfs subvolume, and how many bytes a user can still write there. Why: Every rung of the cheap-worktree ladder starts from these three facts, which the kernel answers in one statfs call and one inode number, so nothing is spawned: a snapshot needs btrfs and a subvolume root (inode 256), a reflink works on btrfs and inside an overlay, and everything else keeps Git’s own checkout. doc/planning/cli-git-btrfs-worktree.md (section “Degradation ladder”) has the measurements behind the ladder.
fs_mount_options
The mount of a path read from /proc/self/mountinfo, with its options (user_subvol_rm_allowed). What: The mount of a path read from /proc/self/mountinfo: its mount point, filesystem type, source and both option lists, so a caller can ask whether user_subvol_rm_allowed is active. Why: The option decides whether BTRFS_IOC_SNAP_DESTROY works for an unprivileged user (the kernel checks the mount’s super options), and the doctor must tell a person which command turns it on. Reading the table in the process spawns nothing. filesystem_identity_mount.rs parses the same table but keeps only the fields it needs and declines the whole table on a shape it does not know; this reader keeps the option fields and skips a line it cannot read, because a missed line can only hide a mount the path is not under. The line grammar is proc_pid_mountinfo(5): id parent major:minor root mount-point options [optional...] - fstype source super-options, with space, tab, newline and backslash escaped as \ooo octal in the path fields.
fs_primitive_error
The typed failure of one Linux filesystem primitive: the call, the path and the error number. What: The typed failure of one Linux filesystem primitive (statfs, a btrfs ioctl, FICLONE, renameat2): which operation, on which path, and the operating system’s error number. Why: The primitives of the cheap-worktree ladder and the subvolume migration all fall through on failure, never panic; the caller decides from the error number whether the next rung applies (the filesystem lacks the feature) or the work must stop (a real fault), and the message names the operation and path a person can act on.
git_builtins
The names of the commands built into Git 2.56.0. What: The names of the commands built into Git 2.56.0, and the test “is this word one of them”. Why: Git never expands an alias whose name is a built-in command, so a built-in word is the command that runs. Any other word may be an alias for anything, including worktree add, and resolving aliases is not ported; the caller needs to know which of the two it is looking at without starting Git.
git_metadata
Captured read-only real-Git queries over raw bytes. What: Run a read-only real-Git query and capture its output as raw bytes. Why: Repository facts (top level, Git directory) come from Git itself, and the paths it prints need not be UTF-8.
git_system_config
The system configuration file a real Git executable reads, learned once per executable identity. What: The system configuration file that one real Git executable reads when GIT_CONFIG_SYSTEM is not set, learned once per executable identity and cached beside the git version cache. Why: Git compiles the path in (ETC_GITCONFIG, /etc/gitconfig for a build with prefix /usr, and under the prefix for any other build, resolved against the executable’s own location in a runtime-prefix build), so no reader may assume /etc/gitconfig. git var GIT_CONFIG_SYSTEM prints the path the executable would read, with the environment’s own overrides removed from its start, so the path is a property of the executable’s bytes and location, which the identity of git_version_cache captures. An executable without an identity (a script, a shim) or an answer that is not an absolute path is unknown, and the readers then leave the question to Git.
git_version
The Git version check run once before work that depends on Git’s behavior. What: The real Git version check, run once per invocation before work that depends on Git’s behavior: a commit, hooks and locks. Why: The native tables and protocols follow Git 2.56.0, and the owner settled on 2.55.0 as the oldest release that runs the swap, so 2.55.0 or newer counts as supported and that read-only commands are never checked (doc/handover/cli-git-rust-implementation.md, “Adopted without a question”).
git_version_cache
The private per-user cache of the version text, keyed by the Git executable’s identity. What: A private, per-user cache of the text git version prints, keyed by the identity of the Git executable that printed it. Why: Every commit starts git version to learn whether Git is new enough. The text is fixed by the executable, so while the executable is byte-for-byte the same file the answer is the same. The identity is the canonical path, size, modification time, change time, device and inode: an upgrade replaces the file (new inode) or rewrites it (new size, times), so a replaced Git is noticed on the next start and a stale entry is never trusted. The cache is used only for a real compiled executable named git: a script, or a shim such as a version manager’s git link to a dispatcher, picks another Git by directory or environment, so its own file proves nothing about the version it will run. The cache lives in the user’s cache directory because the version check runs before any repository is known.
global_arguments
Git 2.56.0 global-argument boundaries over unchanged operating-system strings. What: Locate Git 2.56.0 command boundaries without reconstructing native arguments. Why: Policy stages need the command index, but Git remains authoritative for option validation and -C semantics.
hooks_off
Every real-Git start of the wrapper carries core.hooksPath pointing at a directory that never exists. What: Start the real Git with every hook switched off. Why: The wrapper supports no external hook program: not a traditional hook (an executable file in <git-common-dir>/hooks or a core.hooksPath directory; git lfs writes its four shims there again whenever it cleans a file) and not a hook defined in the configuration (hook.<name>.event with hook.<name>.command). Every real-Git start of the wrapper therefore carries -c core.hooksPath=<a directory that never exists>, which finds no hook file, and -c hook.<event>.enabled=false for every hook event Git knows, which switches every configured hook off whatever its name. core.fsmonitor is left as configured: it is a program Git starts for its own feature, and the event switches do not reach it (measured on Git 2.56.0, doc/planning/cli-git-no-self-invocation-dispatcher.md). They follow every global option of the caller and precede the subcommand, so no -c or --config-env of the caller can switch a hook or the hooks path back on. The options are part of the argument vector, so they add no system call and no process start. The wrapper injects no hook of its own, so pre-push is switched off like every other event.
index_flags
A read of the assume-unchanged and skip-worktree marks of an index file. What: A read of the per-entry flags of an index file that decide whether the index may seed an explicit-path commit: whether any entry is marked assume-unchanged, skip-worktree or intent-to-add. Why: A private index copied from the real index and reset to the base keeps those marks, and git add --all -- <path> then skips a marked file the commit named (assume-unchanged) or refuses it (skip-worktree), where Git’s own partial commit and the previous flow staged it; an intent-to-add entry of a tracked empty file survives the reset and is then left out of the tree, so the commit loses that file. Finding a mark needs the whole file, and a Git start for it would add a process to every commit, so the entries are read here; anything this reader does not fully understand answers “unreadable”, and the caller then builds the index the slower, always-correct way. The layout is the one in Git’s gitformat-index documentation: a 12-byte header, entries (versions 2 to 4), then extensions and a trailing checksum.
index_lock_evidence
Evidence about a foreign index.lock and the verdict it supports. What: Evidence about a foreign index.lock and the verdict it supports: proven alive, dead, or evidence-free. Why: A live owner is proven by a process holding the lock open (device and inode), or by Git’s core.lockfilePid file naming a running process that started no later than the lock changed. A PID file naming a gone process proves a dead owner. Everything else is evidence-free; an absent open holder never proves abandonment, because native git commit keeps index.lock without a descriptor through its hooks and editor (src/index-lock/index-lock-evidence.ts).
index_lock_holder_parsers
Parsers of lsof -F pcDi records and Restart Manager lines, matched by device and inode. What: Parsers of the holder-scan output that is not /proc: lsof -F pcDi records and the Restart Manager script lines, both matched to a lock by device and inode. Why: The parsers are pure text functions, so their controls run on every host, whichever scan produced the text (src/index-lock/index-lock-holders-darwin.ts, -win32.ts).
index_lock_holders
Processes holding a lock file open, found by device and inode. What: Processes holding a lock file open, found by device and inode: /proc/<pid>/fd on Linux, lsof on macOS, the Restart Manager on Windows. Why: An open descriptor proves a live owner. Matching by device and inode, never by path text, means bind mounts and renamed paths cannot fake or hide a holder. Processes the scan cannot read are partial evidence (src/index-lock/index-lock-holders-linux.ts, -darwin.ts, -win32.ts).
index_lock_wait
Waiting for a foreign index.lock under the classification rules and Git’s backoff schedule. What: Git’s own lock-retry schedule: the backoff position, its next step, and the jittered wait it prescribes. Why: A caller that retries a lock the way Git retries its own (the worktree copy’s settlement lock) waits on the same schedule, so it neither hammers the lock nor starves behind it. Waiting for a foreign index.lock is gone: a command that cannot take Git’s index.lock ends as native Git ends it, and the commit service’s landing retries it a bounded number of times itself (src/index-lock/index-lock-wait.ts is the incumbent of the schedule).
index_writer_commands
Index-writer classification and the commands that coordinate with a landing. What: Whether a resolved forwarded command writes the real index. Why: Index writers take the landing lock so they never interleave with a landing commit. The set follows SPEC.md “Index-writer coordination”: add, rm, mv, restore --staged, reset except --soft, stash, checkout, switch, merge, rebase, cherry-pick, revert, apply --cached and apply --index, update-index, read-tree, am, pull and sparse-checkout. Options count only before Git’s --; long options match in full or as the abbreviations Git accepts (src/index-lock/index-writer-commands.ts).
index_writer_forward
Running a coordinated forwarded index writer: the landing lock held while real Git runs as a child. What: Running a forwarded index writer as a child of the wrapper: wait for the commit service’s own hold on the index, complete an isolated merge when one applies, run real Git (again when the service took the index first), and report its status. Why: A writer competes for the index only through Git’s own index.lock, as it does without the wrapper; the one holder that is waited for is the commit service, whose hold ends by itself. The wrapper stays alive around Git so that a run the service beat to the lock can be started again and so that a merge the staged index would make Git refuse can be completed. A command that needs neither replaces the process with Git.
invocation_config
Configuration of the repository one invocation selects. What: Load the policy configuration of the worktree one invocation runs in, and render the events configuration loading can produce. Why: Both wrapped Git commands and git cli-git check/fix need the same answer: what does the cli-git.config.jsonc of the worktree Git reported say.
iso_time
UTC timestamps in the exact form of JavaScript’s toISOString. What: UTC timestamps in the exact form of JavaScript’s Date.prototype.toISOString: YYYY-MM-DDTHH:MM:SS.mmmZ. Why: Transaction owner records carry createdAt and capture records invokedAt in this form; recovery orders dead transactions and the reservation grants turns by comparing these strings, so both wrappers must write the same shape.
journald_native
The native journal protocol and the standard error fallback the service’s entries go through. What: The service’s way into the journal: the native journal protocol over a datagram socket when the service’s standard error is the journal, and a plain line on standard error otherwise. Why: A line on a journal stream socket is stamped by journald when it processes it, so under load the times are seconds late and out of order with the service manager’s own lines. A datagram on the native socket carries the kernel’s time from the moment it was sent (measured in doc/handover/cli-git-logging.md), and the entry can carry a priority and fields a reader filters on (journalctl CLI_GIT_REQUEST_ID=<id>). The protocol is at https://systemd.io/JOURNAL_NATIVE_PROTOCOL/, and $JOURNAL_STREAM is described in systemd.exec(5). No lock is taken: the socket is unconnected and non-blocking, one sendto per entry, and an entry that cannot be sent (journald restarting, a full queue) goes to standard error instead, so a commit never waits for the journal.
js_text
Text operations with the exact semantics of JavaScript’s built-ins. What: Text operations with the exact semantics of the JavaScript built-ins the incumbent applies to strings it shares with the native wrapper. Why: Durable strings such as process birth identities are produced by .trim() in the incumbent; the native wrapper must produce byte-identical strings, and Rust’s str::trim uses a different whitespace set (it trims U+0085 and keeps U+FEFF).
json_record
Compact JSON records read and written with the incumbent’s JSON semantics. What: Reading and writing the compact JSON records both wrappers share, with the incumbent’s JSON.parse and JSON.stringify semantics. Why: Lock, owner, journal and capture records written by either wrapper are read by the other. A record must be read the way the incumbent reads it (numbers are doubles, a whole number in safe-integer range is an integer, the last duplicate key wins) and written with the same field order and escaping.
landed_summary
The one summary line a landed commit prints, with the abbreviated name Git would print. What: The one summary line a landed commit prints, the way git commit prints it: [<branch> <name>] <subject>, with (root-commit) for a commit without a parent and detached HEAD for a detached head, the name abbreviated as Git abbreviates it. Why: The line is printed once, after the service moved the branch, so the name is the commit that landed and not a build that lost a race; a person may copy it. The abbreviation honors core.abbrev and otherwise follows the size of the repository (object_abbreviation.rs), and Git is asked only when the configuration or the object store is not something that reader models.
landing_location
The landing location a forwarded index writer coordinates with: the real index and the registry. What: Where a forwarded command’s real index lives: one git rev-parse that reports the git directory and the index Git would use. Why: An index writer waits for the commit service’s hold on the real index. A command outside a repository, or one writing another index, waits for nothing (src/index-lock/landing-location.ts).
lexical_path
Lexical path normalization. What: Lexical path normalization, the way Node’s path.resolve does it. Why: git cli-git doctor scans each hooks directory once, so one directory named two ways (./hooks, hooks/, a/../hooks) must compare equal without touching the file system.
lfs_bounded_push
The auto-push’s bounded git push, preceded by the upload, its pre-check and its explanation. What: Run one bounded git push of the auto-push, preceded by the Git LFS upload when the repository has one. Why: The auto-push and its reconcile pushes publish refs, so they upload the LFS objects the pushed commits point to exactly as a typed git push does. The wrapper is the parent of both children and captures their output, so it owns the explanation: a missing git-lfs is refused before anything starts, a failed upload ends the attempt with the full explanation after the program’s own output, and the push is not started. The push carries no hook, and no process other than Git and git-lfs is started for this, none of them the wrapper.
lfs_interactive
The Git LFS step before a push the caller typed: pre-check, plan and upload with the caller’s streams. What: The Git LFS step before a push the caller typed: the pre-check, the plan and the upload, with the caller’s streams. Why: The parent owns the whole step, so a refusal, a plan that cannot be worked out and a failed upload are all explained by the process that saw them, and the push is not started after any of them. The configuration the plan reads comes from the snapshot the wrapper already took when it planned the invocation, so a push whose repository is known costs no Git start for it; without a snapshot one config --get-regexp start reads it.
lfs_object_plan
The upload plan narrowed to the objects the push introduces, or to nothing. What: Narrow an upload plan from “scan these refs with git-lfs” to the exact objects the push introduces, decided in process, or to nothing. Why: Most pushes add no LFS object, and then no git-lfs process and no request to the LFS server is needed. The tips come from the plan’s refs; the commits already known to the destination are the remote’s tracking refs when the plan trusts them, else none (the whole history of the pushed refs is scanned, which keeps the case where the tracking refs may not describe the destination correct). When the readers decline, or an object the scan found is missing from .git/lfs (where git-lfs push words the failure and honours lfs.allowincompletepush), the plan stays as it was and git-lfs scans.
lfs_output_mask
Hides the credentials inside URLs in the text relayed from git-lfs. What: Hide the credentials inside URLs in the text the wrapper relays from git-lfs. Why: git-lfs names the LFS server in its errors, and an lfs.url written as https://user:token@host/... carries the token into every one of them. The wrapper relays that text to the caller’s terminal and, for the auto-push, records it in files, so the userinfo of every URL is replaced at the one place where the text leaves the wrapper. The authority of a URL ends at the first /, ?, #, \, quote, angle bracket, backtick or white space, and its userinfo ends at the last @ before that, so an @ in a path, a query or a bracketed IPv6 host is left alone.
lfs_pointer
Whether a blob is a Git LFS pointer, decided as git-lfs decides it. What: Whether a blob is a Git LFS pointer, decided the way git-lfs 3.8.1 decides it when it scans a push. Why: The upload step finds the LFS objects the pushed commits introduce without starting git-lfs. A blob counts when the scanner would hand it to the transfer queue: smaller than the cutoff (lfs/scanner.go, BlobSizeCutoff), a header that names git-media, hawser or git-lfs (lfs/pointer.go, matcherRE), lines in the order version, oid, size with a known version, an sha256: object name of 64 lower-case hexadecimal digits and a non-negative decimal size; a pointer of size zero is skipped by prepareUpload (commands/uploader.go). The path of the blob and .gitattributes play no part. A spelling the port is not sure about (extension lines, non-ASCII edges) is a decline, so the caller falls back to git-lfs itself.
lfs_pointer_scan
The LFS objects a push introduces, found in process from the commits to be pushed. What: The LFS objects a push introduces, found in process: the pointer blobs that the commits to be pushed add. Why: git-lfs push starts a scanner (git rev-list, git cat-file, git ls-remote) and, when it finds anything, asks the LFS server about it; most pushes add no pointer at all, so the wrapper reads the commits itself with the in-process readers and starts git-lfs only when a pointer is found. The commits are those reachable from the pushed tips and not reached before a known-good tip (the remote’s tracking refs, when they describe the destination). Every commit is compared with each of its parents, so a pointer added in one commit and removed in the next is still found. Anything the readers do not fully understand, and any walk that grows past its limits, returns None, so the caller asks git-lfs.
lfs_pre_push
When a push needs the Git LFS upload, and the texts the parent prints around it. What: When a push needs the Git LFS upload, and the texts the parent prints around it. Why: Traditional hook files are unsupported, so the pre-push shim git lfs install writes cannot be relied on. Without an upload step a push publishes pointer files whose objects the LFS server never received. The wrapper therefore runs git-lfs push <remote> <refs> itself, as a child it owns, before it starts the push (lfs_upload.rs), only when the Git configuration holds LFS keys; the push carries no hook and no injected configuration, the wrapper never starts itself and writes no shell text (doc/planning/cli-git-no-self-invocation.md).
lfs_push_precheck
The check that git-lfs can start, made before a push that is preceded by the upload. What: The check, before anything starts, that the Git LFS upload a push is about to run can start. Why: A push of a repository with LFS keys is preceded by git-lfs push, which the parent starts. When no directory of PATH holds git-lfs, one stat per PATH directory, with no process started, finds that first, and the parent says so with the full explanation. A push that bypasses the upload (--no-verify, GIT_LFS_SKIP_PUSH, or the switch -c hook.cli-git-lfs-pre-push.enabled=false) needs no program, so it is never refused here.
lfs_server_retry
Which failed git-lfs push runs are tried again (server errors only), after how long, and the notice. What: When a failed git-lfs push is tried again: the server-error classification, the delays and the notice. Why: The LFS server of a repository can answer HTTP 5xx for a moment, and git-lfs treats such an answer as fatal for the run (lfshttp/errors.go: a status above 499 other than 501, 507 and 509 becomes a fatal error), so a push that was fine a second later fails. Uploads are content addressed, so a second run never sends an object twice. The retry is bounded twice over: a short fixed list of delays, and a classification from the program’s own output that never retries a refused credential, a missing local object, or any failure that is not a server error.
lfs_upload
The parent’s bounded or interactive run of the Git LFS upload. What: The parent’s run of the Git LFS upload before a push: git-lfs push <remote> <refs> as one child it owns. Why: Without an upload step a push publishes pointer files whose objects the LFS server never received. The wrapper runs git-lfs push itself, so a failure is explained by the process that saw it and the push, which then carries no hook, is never started. Nothing here starts the wrapper, writes shell text or injects Git configuration (doc/planning/cli-git-no-self-invocation.md). What to upload is decided in lfs_upload_plan. The child sees what the caller gave Git: the global -c options travel as GIT_CONFIG_COUNT pairs, --git-dir, --work-tree and --namespace as their variables, -C as the working directory, and the real Git’s directory leads its PATH so a git it starts is never the wrapper while git-lfs itself is the one the caller’s PATH finds first.
lfs_upload_plan
What the Git LFS upload sends: the remote and the refs, read from the push and the configuration Git would read. What: What the Git LFS upload before a push sends: the destinations git lfs push takes and the refs whose objects it uploads, with --all wherever a remote-tracking ref of this clone may not describe the destination, so that it cannot hide an object the destination lacks, and in git-lfs’s bounded form (no --all) everywhere else. Why: git lfs push takes refs and no ref-update lines, so the plan is read from the push’s own arguments with the same option table as the rest of the wrapper, and from the configuration Git itself would read for the parts the arguments leave out (the default remote, remote.<name>.push, remote.<name>.mirror, the push URLs, push.default). The configuration comes from the snapshot the caller already holds, or from one query. Refs that Git expands from a pattern are listed with git for-each-ref and matched here with a refspec’s own rule (* crosses /). The plan fails closed: a query that fails is an error the caller turns into a refusal, never into “nothing to upload”. Only a mirror push uploads every ref.
lfs_upload_settings
The configuration the Git LFS upload plan reads, and the default remote derived from it. What: The configuration the Git LFS upload plan reads, and the answers derived from it: the default remote, the branch HEAD names, and whether a value reads as true. Why: The plan and the manual-push probe must agree on the destination of a bare push, so both read it here. The entries come from a snapshot the caller already holds or from one config --get-regexp, never one start per key.
maintenance_decision
Automatic maintenance decision: config record parsing and the enable and detach rules. What: The decision behind automatic maintenance after a landing: parsing git config --null --get-regexp records, and choosing whether git maintenance run --auto runs and whether it detaches. Why: Native git commit runs this step after updating HEAD; a landing replaces that commit with plumbing, so the pack count grows by one per commit unless this step runs. The rules mirror Git’s prepare_auto_maintenance in run-command.c, including JavaScript number semantics for values the incumbent reads with Number(...).
maintenance_run
Running automatic maintenance after a landing: the config read, then git maintenance run --auto. What: Running automatic maintenance after a landing: one git config read for the maintenance keys (or the commit’s configuration snapshot), then git maintenance run --auto --quiet, detached or not, in the real worktree. Its exit status is returned for the caller to ignore, as native Git does. Why: Every landing migrates its objects into one pack; without this step the pack count grows by one per commit and every later Git process searches every pack. Failures are logged and never fail the landed commit.
management
The git cli-git decision: help, retired commands, and direct check/fix. What: Decide what one git cli-git ... invocation does. Why: Help, refusals and retired commands need no repository; check and fix validate their policy selection and configuration, then run one policy pass over the selected repository and report its events on standard output.
management_arguments
The git cli-git argument grammar. What: The grammar of git cli-git <command>, the wrapper’s own management namespace. Why: check and fix run policies over an explicit scope; doctor lists the traditional hooks a repository holds; trust, untrust and status are retired with executable configuration and only explain that.
management_selection
The refusal of a direct command whose --policy selects a policy that is off. What: The --policy selection of a direct git cli-git check or fix: the names turned into shipped policies, and the refusal of a selection that names a policy which is off. Why: A mistyped policy ID must stop the command instead of selecting nothing. A policy that is off runs nothing, so a run limited to it would exit 0 with no output and read as “checked and clean”. A CI step or a person who selected the policy by name must learn that it did not run, and which cli-git.config.jsonc key turns it on. The commit, add and push lifecycles are not direct runs and keep skipping policies that are off.
manual_push_lifecycle
The manual-push candidate scope of one push: the commits and trees the destination lacks. What: The manual-push candidate scope of one git push: the commits and trees the destination lacks. Why: The manual-push policies (final-newline, forbidden-strings) scan the content a push publishes. The incumbent (src/policy-engine/manual-push-lifecycle.ts) discovers the updates, bounds each by what its destination already has, and hands the scan to the policy engine; a failure stops the push with exit 2.
manual_push_porcelain
Authoritative manual-push updates discovered by a private dry-run push and confirmed with ls-remote. What: The parser of git push --dry-run --porcelain output, which tells the manual-push probe what a push would send. Why: The probe used to learn the update lines from a recording hook written as shell text. Git’s own dry-run report carries the same facts without any program of ours: for each destination a To <url> line, then one tab-separated line per ref (<flag>\t<from>:<to>\t<summary>), then Done. With core.abbrev=no the summary of an update holds the full old and new object names (old..new, or old...new when forced); a new ref, a deletion and a refusal hold a bracketed word instead.
manual_push_probe
The updates a manual git push would send, learned from Git’s own dry-run report and confirmed against the remote. What: The authoritative ref updates a git push would send, discovered by a dry-run push whose report Git writes itself. Why: The manual-push policies must scan exactly what Git would publish, so the probe asks Git: git push --dry-run --porcelain reports each destination and ref update (manual_push_porcelain), the local objects come from that report or from one rev-parse, and git ls-remote --refs confirms each destination value. A remote ref that changed between negotiation and confirmation stops the probe. No program of ours is started for this, and no hook or shell text is written: an earlier version recorded Git’s update lines with a #!/bin/sh hook (doc/planning/cli-git-no-self-invocation.md). Every Git child here is bounded by the query deadline, so a remote that never answers cannot hold the push.
manual_push_range
Published-set and range computation for manual-push scans: which pushed commits a remote does not have. What: The commits a push newly publishes, relative to what each destination remote already has. Why: The manual-push policies scan only what becomes public. A pushed commit is already published when a remote-tracking ref of the destination, or an authoritative prior value of an update to that remote, reaches it (src/policy-engine/manual-push-published.ts). The rest of the history is the scan’s range.
markdown_exclude
The gitignore-syntax exclude patterns of markdown/autofix, matched as the linter matches them. What: The exclude option of markdown/autofix: gitignore-syntax patterns, relative to the repository’s top level, naming candidates the policy leaves alone. Why: The installed wrapper filtered candidates with the ignore package before starting the linter (rewrite-candidates.ts, rewriteCandidates), so an excluded file never starts a linter and never fails for one. The linter evaluates its own copy of the option with the ignore crate’s gitignore matcher and a walk over ancestor directories (package/linter/monochromatic-lint/src/markdown_lfs_patterns.rs); this module uses the same matcher and the same walk, so both agree on every path.
markdown_linter
The executable’s linter for that policy: monochromatic-lint linked in-process, guarded by a deadline, once per candidate. What: The linter the executable gives markdown/autofix: monochromatic-lint, linked into the executable and run in-process once per candidate, on a guarded thread with a deadline. Why: The owner’s rule is that the wrapper embeds the repository’s own tools and starts other programs as rarely as possible. The linter’s library entry monochromatic_lint::run_embedded::fix_source is the executable’s --config --stdin --stdin-filename --fix run without the program start (package/linter/monochromatic-lint/src/run_embedded.rs), so there is no program to find on a search path, no configuration file to write and no stream contract to parse. The child process used to be the wrapper’s kill boundary; markdown_linter_guard.rs replaces it with a thread, a deadline and panic containment, which is a hang and panic boundary and not a crash boundary: a stack overflow or an abort in the linter ends the wrapper. The size of a finished result is checked against the former child’s output bounds (markdown_linter_output.rs), after the linter has produced it. The configuration is rebuilt from the policy’s options for each candidate, which costs a small JSONC document, so no state outlives the options it was built from.
markdown_linter_config
The one-rule linter configuration document. What: The linter configuration the markdown/autofix policy hands monochromatic-lint: one block that selects the policy’s rules for Markdown and MDX files, as JSONC text for the linter library’s in-process entry. Why: The plan’s contract (doc/planning/unified-linter.md, section “Distribution and consumers”) is a one-rule configuration of the policy’s own, so a configuration the repository keeps for its own linting never changes what the commit-time policy does. Each rule runs at warn, so a finding the fixes leave is a warning finding and the run counts as usable, and only a run that could not finish fails. The document is built as a value and printed by the JSONC emitter, so no pattern text can break out of its string.
markdown_linter_guard
A job on a thread of its own with a deadline and panic containment. What: Run one job on a thread of its own and wait for it with a deadline, so a job that panics or never ends costs the caller one failed result, not the process. Why: The Markdown linter used to run as a child process, and the operating system gave the wrapper a free boundary: a child that hung could be killed and a child that crashed could not take the wrapper down. Linked into the wrapper, the linter shares its address space, so a boundary is rebuilt here from what the language offers, and it is a smaller one than a process gave: a hang and a panic are contained, a stack overflow, an out-of-memory abort or any other abort is not, and ends the wrapper like any other crash. The size of a finished result is bounded by the caller after the fact. The long-term answer is to run the linter in the resident service, outside the client. A panic is caught on the job’s thread and returned as text. A job that outlives its deadline cannot be stopped from outside, so the wrapper stops waiting for it and leaves the thread to end with the process, which is short-lived; the thread owns everything it uses and shares nothing with the caller, so abandoning it leaves nothing locked. The thread has the linter’s own stack size, because the main thread’s stack is set by the platform and a stack overflow cannot be caught; the size makes an overflow as unlikely as in the executable, it does not contain one.
markdown_linter_output
What one in-process linter run produced: the fixed source and the findings left, or why it is unusable. What: Read what one in-process monochromatic-lint fix run produced: the fixed source and the findings it could not fix, or the reason the run cannot be trusted. Why: The linter’s own contract (package/linter/monochromatic-lint/src/run_embedded.rs) is the executable’s --stdin --fix run with its one-rule configuration: status 0 with the fixed source and warning findings, because the policy’s configuration gives the rule severity warn; 1 only for error findings or a warning limit, neither of which that configuration can produce; and 2 for a run that could not finish. Anything else, and any finding that is not a warning of a selected rule with a position, means the run cannot be trusted, so the policy fails instead of passing an unchecked file.
merge_isolation
A true merge completed in isolation while the index holds staged changes. What: A true git merge <branch> completed in isolation when the index holds staged changes that make native Git refuse it: the merge is computed from objects, committed, and applied with Git’s own two-way tree update, which keeps every entry and every file the merge does not change. Why: Several sessions share one checkout. Native Git refuses every merge that creates a commit while the index differs from HEAD at all (measured in doc/planning/cli-git-merge-with-staged-index.md), although the merge may touch none of the staged paths. A fast-forward is not refused for unrelated paths, because it is a two-way update that keeps entries it does not write; a merge commit whose first parent is the current commit is exactly such an update. So the merge tree comes from git merge-tree --write-tree, the commit from git commit-tree, and the checkout moves with git merge --ff-only <commit>, which refuses when the branch is no longer where the merge was computed, after a check that no changed path is staged or modified by someone else. An intent record, written before Git starts, lets recovery finish or discard a merge whose process died.
merge_isolation_changes
The raw differences of an isolated merge and the worktree state of each changed path. What: The raw tree differences between the two commits of an isolated merge, and the state of a worktree path against them. Why: Recovery of a merge killed while Git was updating the worktree must tell, for each path the merge changes, whether the worktree still holds the old content, already holds the new content, or holds something else that belongs to someone else and must not be overwritten.
merge_isolation_journal
The durable intent record of an isolated merge. What: The durable intent record of an isolated merge: the commit it created, the branch value it expects, and the process that runs it. Why: Between the moment Git starts updating the worktree and the index for the merge commit and the moment the branch moves, a killed process leaves a worktree and an index that agree with the merge while the branch does not. The record, written and synced before Git starts, lets the next command decide whether the merge never started (discard it) or must be completed. Records are created once and never rewritten, like the commit transaction’s own state files.
merge_isolation_message
The commit message of an isolated merge. What: The text of an isolated merge’s commit message and the name Git gives the merged commit in it. Why: The merge commit must read exactly as native Git would write it: the default subject comes from git fmt-merge-msg, which git merge itself uses, fed the description line git merge builds for the commit-ish (branch 'x', tag 'x', remote-tracking branch 'x' or commit 'x'), and any message is cleaned the way a commit that opens no editor cleans it. A commit-ish whose description cannot be built the same way is not guessed: the caller forwards the command.
merge_isolation_recovery
Recovery of an isolated merge whose process died. What: Recovery of an isolated merge whose process died: discard it when it never touched the worktree or the index, complete it when Git had started to update them. Why: The merge commit exists before the branch moves, and Git updates the worktree and the index of the paths the merge changes before it moves the branch. A process killed in between leaves files and index entries of the merge beside a branch that still names the old commit, which the next commit would then revert. Completing the merge from its record finishes exactly what Git had started, for exactly the paths the merge changes, and leaves every other staged entry and every unstaged change as it was. A path that holds neither its old nor its new content belongs to someone else, so recovery stops and keeps the record instead of overwriting it.
merge_isolation_report
The refusal findings of an isolated merge. What: The refusals of an isolated merge, as the finding events a caller reads, each naming the paths involved and every valid way forward. Why: A merge that cannot proceed changes nothing, and the person (or the agent) who ran it must learn which files stand in the way and what to do next without reading the wrapper’s source.
merge_options
The git merge command lines the wrapper completes in isolation, and those it leaves to native Git. What: The shape of a git merge command line that the wrapper can complete in isolation, or the reason it cannot. Why: The isolated merge reproduces what native Git writes only for options whose effect is known exactly: the message, the fast-forward choice, signing and the unrelated-histories switch. Any other option (a strategy, --squash, --no-commit, --ff-only, --edit, an abbreviation Git would accept) is not guessed: the command is forwarded to native Git unchanged, with the refusal it may give.
object_abbreviation
The abbreviated object name Git prints (core.abbrev, the automatic length, uniqueness), computed without starting Git. What: The abbreviated name Git prints for an object (git commit’s [main 30b2211], git log --oneline), computed in process: the length from core.abbrev or from the number of packed objects, then lengthened until no other object shares the name. Why: A fixed seven characters turns ambiguous as a repository grows (Git itself prints ten in a repository of some hundred thousand objects), and a person may copy the name. Git’s rules are odb_find_abbrev_len (odb.c) for the length and find_abbrev_len_for_pack (odb/source-packed.c) and odb_source_loose_find_abbrev_len (odb/source-loose.c) for the uniqueness, all read from the Git 2.56.0 sources. The count is the approximate one: the objects of the pack indexes (a multi-pack-index counts for the packs it covers), loose objects not counted, alternates included. Reading the indexes needs no Git process; a shape this reader does not model (an incremental multi-pack-index chain, a version 1 pack index with a name it cannot read) answers nothing and the caller asks Git.
owner_lock_record
The owner record inside every owner-lock directory. What: The owner record inside every owner-lock directory (owner.json, schema version 1): its strict parser, its exact encoding, and the owner-liveness rule. Why: The landing, reservation, hook, capture locks are directories holding this record. The incumbent and its hook dispatcher write and read the same record (src/owner-lock/owner-lock-record.ts), so both wrappers contend on one lock and each retires the other’s dead lock.
panic_notice
The executable’s panic hook: where an internal error happened, never its message. What: The panic hook of the executable: one line naming where an internal error happened, never its message. Why: The linked scanner catches a matcher panic and reports it as a failed scan, but Rust runs the process’s panic hook before the panic can be caught, and the default hook prints the panic’s message, which can hold bytes of the file being scanned. The scanner’s own executable installs a hook that prints nothing; this one keeps the source location, which holds no repository content, so a wrapper defect is still visible.
path_bytes
Exact repository path bytes as the operating system takes them, and as text for messages. What: Exact repository path bytes as the operating system takes them, and as text for messages. Why: Git allows any bytes in a file name. The commit path keeps a path as Vec<u8> from Git’s listing to Git’s next argument, so a name that is not UTF-8 reaches Git unchanged; only a message for a person shows the text with replacement characters.
pid_namespace
The PID namespace an owner record names, and the rule for owners of another one. What: The PID namespace an owner record names, and the rule for judging an owner that lives in another one. Why: A PID means something only inside its PID namespace. A wrapper in a container that probes a host owner’s PID finds no process (or an unrelated one), so it would judge a live owner dead and recover or retire what the owner still holds (issues 1781 and 1784). A record therefore names its namespace, and an owner of another namespace is judged by the age of its record instead of by the PID.
policy_added_path
Whether a direct fix may change a tracked file it did not select. What: Whether a direct fix may change a tracked file it did not select, and the patch-conflict message when it may not. Why: SPEC.md (“Added paths”) admits such a file only when HEAD, the real index, the private index and the worktree all hold the blob the correction was computed against, so the fix discards nobody’s change. The reasons and the remedy are the installed wrapper’s words (commit-transaction-added-paths.ts, assertAddablePath and its direct-fix message), checked in its order.
policy_catalog_specifiers
The optional catalog-specifier policy over the shared rule crate: every dependency in a tracked package.json is a catalog or workspace reference. What: The optional mono/catalog-specifiers policy: every dependency in a tracked package.json is catalog:, catalog:<name> or exactly workspace:*, and no package.json5, package.yaml or package.yml is tracked. Why: pnpm install accepts a literal version without a word, so the repository checks the rule itself (doc/handover/catalog-specifier-policy.md). The rule, its findings and their wording belong to the monochromatic-catalog-specifier crate, which the pnpm wrapper shares; this module only hands it each candidate manifest’s path and its candidate bytes, never the worktree’s, and turns its findings into the engine’s. Only the last path segment decides what a file is, and it is decided on the path’s raw bytes, so a manifest below a directory whose name is not UTF-8 is still checked.
policy_checks
The shipped policies as checks over rule cores and repository facts. What: The shipped policies as the engine sees them: one check per policy, built from the pure rule cores and the repository facts they ask for. Why: The engine owns order, severity and stopping; the rule cores own decisions from arguments; this module joins them and fetches a repository fact only when a core asks. The content policies read the lifecycle’s candidates: they report nothing where a lifecycle has none. The Markdown policy checks the candidates it is given with the native Markdown linter.
policy_content
What a lifecycle offers content policies, and its candidates once prepared. What: What one lifecycle offers the content policies, and the candidates it prepared for them, read once and shared by every content policy of the invocation. Why: A forwarded command other than git add has no candidates, so its content policies find nothing. git add and the direct commands have candidates, which are prepared the first time a content policy reads them: an invocation whose content policies are all off or escaped starts no Git process for them. A failure to prepare is remembered, so it is reported once and never retried.
policy_convergence
The bounded loop that repeats a pass after corrections changed candidate content. What: The bounded loop that repeats a whole policy pass after corrections changed the candidate content, until the content is stable or the loop gives up. Why: A correction by one policy can create a finding for another, so every changed state restarts the complete order. The loop must end: at most eight passes may change the content, and returning to an earlier state is a cycle. This module owns only that control flow; running policies, applying corrections and storing exact states stay behind a small interface, so the loop is testable without Git.
policy_dependent_version
The mono/dependent-version-bump check over a lifecycle’s candidate state. What: The mono/dependent-version-bump check: the native planner over the lifecycle’s prepared candidate state, its findings, and the corrections a direct fix applies. Why: The installed wrapper’s policy (dependent-version-bump-policy.ts) reports one finding per publishable dependent of a raised manifest, each with a full-content patch, on direct-check and direct-fix; on pre-forward it plans only for a forwarded commit. A real git commit runs through the commit transaction instead of being forwarded, so pre-forward reaches this check only for git add, where the policy finds nothing.
policy_engine
Ordered policy execution for one lifecycle point. What: Run shipped policies in registry order for one lifecycle point and settle the outcome: which events were produced and whether the command may proceed. Why: Every lifecycle shares one execution contract: a policy runs only for the triggers it declares, at the configured severity, unless it is filtered out, escaped or off; the first error finding stops the pass unless the caller asked to keep going; and a policy or lifecycle this executable cannot evaluate is reported as unavailable, never as clean.
policy_events
Policy events and their JSON Lines rendering. What: The events one policy pass produces, and their JSON Lines rendering. Why: Agents and scripts read these lines from wrapper standard error and from git cli-git check|fix standard output. Each event is one compact JSON object with the incumbent’s field names and field order, so existing readers parse native events unchanged. Events are numbered when rendered, so a list of events always prints with consecutive numbers in emission order.
policy_final_newline
The built-in final-newline policy: canonical final LF, preserved paths, findings. What: The built-in final-newline policy: a non-empty UTF-8 text file without NUL bytes must end with exactly one LF byte. Why: The rule, its finding and its generated-output path families are the installed wrapper’s (final-newline-policy.ts, final-newline-normalize.ts), so the native wrapper reports the same files with the same words. A file whose text Git attribute is unset (-text, which binary includes) keeps its exact bytes, which the installed wrapper could only do by path; the attribute travels with a package that moves, where a path list goes stale. The normalized bytes are also the full-content correction a fixing lifecycle applies.
policy_forbidden_strings
The optional forbidden-strings policy over the linked scanner, with redacted findings. What: The optional security/forbidden-strings policy: scan every candidate’s exact bytes and pathname through the linked scanner and report each match, redacted. Why: The installed wrapper wrote candidate bytes to temporary files, started the scanner executable and parsed its standard error. Here the same scanner is a library: rules are loaded once per invocation, only when a candidate can be scanned, and each finding carries the scanner’s masked display path and an opaque rule name, never the matched text. A scan that could not complete ends the policy with the code of its cause, never as a clean result.
policy_markdown
The markdown/autofix policy over candidates, behind its linter interface. What: The optional markdown/autofix policy: every Markdown candidate the lifecycle offers is passed through monochromatic-lint --fix with the policy’s rules; a changed source is reported, and corrected on git cli-git fix, and each finding the fixes leave is reported as it is. Why: The installed wrapper’s markdownLintPolicy (package/git-policy/markdown-lint/src) decides the behaviour: deleted files, symbolic links and submodules are skipped, and so are excluded paths before any linter starts and contents that are not UTF-8; candidates are linted one at a time, in candidate order; the codes and words of both findings are kept. One thing differs by design: the extension test is the linter’s own (.md and .mdx, exact case), so no file is sent that the linter refuses. The autofix finding keeps the installed wrapper’s words, which name its former linter, so both wrappers report the same file the same way while both are in use.
policy_pass
One complete pass: built-in policies, fixed transforms, optional policies. What: One complete policy pass over the shipped policies: the built-in policies, then the fixed argument transforms of a forwarded command, then the optional policies. Why: Every lifecycle runs the same three stages in the same order, and each stage can end the pass. Keeping the sequence in one place means a wrapped command and a direct git cli-git check cannot drift apart in what they run or when they stop.
policy_registry
The fixed registry of shipped policies. What: The fixed registry of policies compiled into the native wrapper. Why: Repository JSONC can only tune shipped policies; it can never name code to run.
policy_root_context
The optional root-context policy: no top-level CONTEXT.md enters the index. What: The optional mono/forbidden-root-context policy: a CONTEXT.md file at the repository’s top level must not enter the index or be checked. Why: This repository keeps no context files; agents read the source on every probe (doc/agent/domain.md). The rule and its finding are the installed wrapper’s (package/git-policy/repository/src/index.ts): only the top-level pathname counts, and removing the file is always allowed.
policy_transforms
The fixed argument transforms of a forwarded command, in order. What: The fixed argument transforms of a forwarded command, in their fixed order: atomic push, commit-only, status hints off. Why: These are not policies: they have no severity and cannot be configured. They add --atomic to git push, -o to git commit and -c advice.statusHints=false before git status, and the commit-only transform can reject a commit. The decisions belong to the pure rule cores; this module runs them in order and fetches the two repository facts commit-only may ask for.
policy_trigger
Lifecycle triggers and the trigger set of every shipped policy. What: The lifecycle points at which policies run, which shipped policy runs at which point, and which points this executable can serve. Why: A policy runs only for the triggers it declares. A trigger whose lifecycle is not ported must be answered as “unavailable”, never as an empty result that would read as “checked and clean”.
post_commit
The post-commit gate over a landed commit: its events, its blocking and the backup it allows. What: The post-commit policy gate over a landed commit’s own changes. Why: The incumbent (src/policy-engine/post-commit-lifecycle.ts) checks the committed bytes of every path a landed commit changed, and only a gate that passes lets the commit reach auto-push. The push itself is the service’s work item, handed over by commit_command.rs after a passing gate. A blocked gate keeps the commit local and names it in a commit-landed event; the command exits 2. The landed commit is an input, so the integrator can call this after any landing.
private_index_request
The private index and object store a commit’s preparation selects, and the Git request that carries them. What: The private index and object store a commit’s preparation reads and writes, and the Git request that selects them. Why: Staging and listing read the private commit index, and only staging writes objects, into the scratch object store; GIT_INDEX_FILE and GIT_OBJECT_DIRECTORY select that private state while the invocation’s global options are ignored, as the incumbent does.
private_index_stage
Slice 6: writes one corrected file into the private commit index as a blob and an index entry. What: Write one corrected file into the private commit index: its bytes as a blob, then the index entry that names that blob. Why: Corrections land in the private index, never in the real index or the worktree. The incumbent patches the private index with git apply --cached; here the policy already holds the corrected bytes, so the blob and the entry are written directly.
private_storage
Private, crash-safe file primitives shared by every durable record. What: Private, crash-safe file primitives shared by every durable record of the wrapper: exclusive no-follow creation with private modes, file and directory sync, and no-follow reads that refuse links and non-regular files. Why: Lock records, transaction journals and capture records must survive a crash in a readable state, must never be written through a planted link, and must be private to the account. The incumbent’s src/trust/registry-io.ts defines these semantics; both wrappers write and read the same files.
process_identity
Process-birth identities in the incumbent’s exact spelling. What: Process-birth identities: a string that names one process for its whole life and changes when its PID is reused, in the incumbent’s exact spelling. Why: Every lock and transaction records its owner’s PID and birth identity; an owner is alive only while its PID names a process with the recorded identity. Both wrappers judge each other’s locks, so the strings must agree byte for byte (src/policy-engine/commit-transaction-process-identity.ts; the decision in doc/handover/cli-git-rust-implementation.md, “Adopted without a question”).
process_start
Process start times for lock owners, read from /proc, ps or the Windows process table. What: The wall-clock start time of a process, with its resolution, for comparing a Git lock PID file’s owner with the lock’s change time. Why: A PID file names a live owner only when that process started no later than the lock changed; a younger process reused the PID. Linux derives the start from /proc/<pid>/stat field 22, /proc/uptime and the current time; macOS reads ps -o lstart=; Windows reads Get-Process StartTime (src/index-lock/process-start-time.ts).
process_tree
The processes below one process, read before it is stopped and signalled only while they are still the ones found. What: The processes below one process: found before it is stopped, and signalled afterwards, each only while it is still the process that was found. Why: Stopping a process does not stop what it started. A git push that is sent SIGTERM or SIGKILL leaves its transport helper (git-remote-http, ssh) and the hooks it ran (git-lfs) alive, orphaned to the init process, holding their connection until their own timeout ends them (measured: a helper stayed 60 seconds, a git-lfs that never exits stayed for good). The tree has to be read before the root dies, because a child’s link to its parent is gone once the parent has exited. A process number can be reused, so each process is identified by the start time the kernel recorded for it, and a number whose start time changed is not signalled. The list comes from /proc, so only Linux finds any; elsewhere the tree is empty and only the root is stopped.
process_use
The processes of this host that use a directory or a file. What: The processes of this host that use a directory or a file: a working directory or an open descriptor inside the directory, or an open descriptor on the file. Why: Recovery may treat a directory or a lock as abandoned only when no process can still be using it. A process that holds a path open is the one proof of use that survives a lost record, so the scan reads it from /proc and says what it could not read instead of guessing. Only processes of the scanning account are reported as unreadable: another account’s process cannot have opened a private file this account created, and every unprivileged scan meets some of them.
push_deadline_advice
The words that say how to change the push deadline, for the terminal and for the resident commit service. What: The words that tell a person how to change the push deadline, for the program that is running the push. Why: A push in a terminal reads CLI_GIT_PUSH_DEADLINE_SECONDS from that terminal’s environment, but the resident commit service pushes after the commit command returned and reads it from the environment the user manager gave the service when it started, so advice to set the variable is not something a person can act on there. In the service the advice names the user manager’s own command and the restart that makes the service read the new value.
push_failure_notice
The record of a failed backup push and the rule for when a wrapper command shows it. What: The record of a backup push that failed after its commit was saved, and the rule for when the wrapper shows it: the resident service writes one record per failed push under the Git directory of the worktree whose commit it was, a later wrapper command in that worktree shows each record once on standard error, and a later push of the same branch that succeeds removes the records of that branch. Why: The push runs in the service after the commit command returned, so nothing is attached to the command that could print its failure; without a record the person would never learn that their backup push was refused (doc/planning/cli-git-resident-service.md, “After-commit work”). Records are plain files in the worktree’s own Git directory so the check on every command is one directory read that finds nothing when nothing failed. A record holds the branch, the whole commit name and Git’s words with credentials already masked; the wrapper words the notice when it shows it, so the commit name is abbreviated the way Git abbreviates it in the repository as it is then. Records are shown on standard error only, and not at all to a command whose output a program reads (--porcelain, -z, plumbing), so that program neither sees the text in its output nor consumes the record before a person can read it.
push_stacking
An explicit push that lost a race: run as a child, merge in what landed on the remote and publish the merge. What: An explicit git push that lost a race: the commits that landed on the remote since the local base are merged in the object store, and the merge is published by the wrapper pushing it again through its own gates. Why: Several sessions push to one branch all day; a push rejected because the remote moved cannot succeed again, and every caller would fetch, merge or rebase and retry by hand while racing the others. The wrapper runs an eligible push as a child with the caller’s streams, so Git’s own output is unchanged. Only a failed push is looked at again, and only a divergence of the destination is acted on; an authentication, offline, hook or rule refusal keeps the exit status and the text Git gave. The merge is pushed through the same push planning, run inside this process (stacked_merge_push) with STACKED_PUSH_MARKER set in the planned environment, so the policies that gate any push read the integrated result and the marker stops that push from stacking again. The wrapper never starts itself for this. A push that carries the Git LFS upload also runs here, as a child, so this parent can explain a failed upload.
push_trigger_log
The record of each decision of the push trigger: handed over, or declined with its reason. What: The record of every decision of the push trigger after a command that adds commits: handed over, or declined with the step that declined and why. Why: A merge that was never pushed left nothing to look at, because the trigger returned silently at eight places and the reason sat only in the code. The record is one JSON line appended to a size-capped file in the common Git directory beside the commit timing records, and a debug line under MONOCHROMATIC_VERBOSE=true; the person’s terminal shows neither.
random_id
Random identifiers in the layout of the incumbent’s randomUUID. What: Random identifiers in the layout of the incumbent’s randomUUID: lock tokens and the unique names of worktree-copy and auto-push scratch files, and the check for that layout. Why: A lock token must be unguessable and unique per acquisition, and the startup step recognizes the directories an earlier release named with a transaction ID in the same canonical layout (is_canonical_uuid, startup_gate.rs).
real_git
PATH resolution of the real Git executable. What: Find the real Git executable on PATH without ever selecting this wrapper. Why: The wrapper is installed as git ahead of real Git; forwarding needs the first usable candidate that is not a cli-git wrapper.
real_git_candidate
Wrapper self-exclusion for one PATH candidate. What: Decide whether one PATH candidate is real Git, a cli-git wrapper, or unusable. Why: The wrapper shadows git on PATH and must never select itself, a copy of itself, or a script that starts the TypeScript wrapper.
recovery_error
The fail-closed failure of reading or recovering durable transaction state. What: The fail-closed failure of reading or recovering durable transaction state. Why: Malformed, unsafe or contradictory state is never guessed at or deleted: the command stops with exit status 2 and a message naming the path, and the state stays for inspection (CommitTransactionRecoveryError in the incumbent; implementation plan, “Commit transactions and recovery”).
recovery_files
No-follow file operations recovery and landing share. What: The small file operations the shadow, the pack keep files, the conclusion copies and the worktree installs share: existence probes, an optional read, removal of one file, and the name of Git’s PID file beside a lock. Why: Each fails closed with the path and the operation named (RecoveryError), as the incumbent’s commit-transaction-recovery-files.ts does.
ref_files
Files-backend ref reads that answer without a Git process or decline. What: Reads of the files ref storage that answer a question without a Git process, and refuse to answer anything they cannot prove equal to Git’s answer. Why: git symbolic-ref --quiet HEAD only reads the worktree’s HEAD file and, for a branch, checks that the branch is not itself a symbolic ref. A strict reader of the plain spellings saves a process on every commit, and every other spelling (a different ref name shape, a link, a symbolic chain, reftable storage) returns None, which sends the caller to Git.
replay_capture_order
Replay: capture order over the landed first-parent history and the per-path decision (replay_capture_order). What: The capture-order decision for one shared path of a replay. Why: When the prepared commit and every landed commit that changed a path captured it from the same worktree, the later capture’s bytes land, which is what sequential native commits record from the shared disk. Every other path keeps subsumption and then the three-way merge. The service decides it from the capture stamps it keeps (service_captures.rs); this is the answer’s shape.
replay_containment
Replay: the one-sided extension of subsumption over zero-context hunks (replay_containment). What: The context-tolerant half of replay’s subsumption: whether each prepared change is the landed change extended by the prepared commit’s own edit on one side. Why: Strict reverse application rejects a prepared file holding the landed edit plus an own edit next to it, because that own edit sits in the landed hunk’s context. Reading both changes as zero-context hunks in base coordinates accepts exactly the one-sided extensions and still rejects own edits on both sides and modify/delete conflicts.
replay_merge
Replay: the merge tree, the first conflicting landed commit, and the replayed commit rebuild (replay_merge). What: The merge step of one replay: the tree from git merge-tree --write-tree of the prepared change onto the landed tip. Why: A commit that lost the race to another is prepared again over the tip the service names; the merge says whether the two changes meet cleanly and, when they do not, which paths conflict, in byte order.
replay_patch
Replay: the unified-diff parser of subsumption (replay_patch). What: The unified-diff parser that replay’s subsumption reads: git diff-tree -p output over single-level trees named by decimal index, split into hunks per entry name. Why: Subsumption decides from hunk lines and their markers; the parser keeps every byte exact, so a hunk line round-trips to the blob bytes it came from.
replay_reverse_apply
Replay: the exact strict reverse check of subsumption (replay_reverse_apply). What: The exact emulation of git apply --reverse --check for one text file: the subsumption test that asks whether a landed change already sits in the prepared bytes. Why: Git’s apply.c matching (apply_one_fragment, find_pos, match_fragment) with its defaults: no context reduction, no whitespace fixing, no overlapping hunks, and anchoring at the file’s start and end. Replay must give the same answer as Git for the same bytes.
replay_shadow
Replay: the shadow-repository Git runner that every replay step uses (replay_shadow). What: Running real Git against the shadow repository for replay: the shadow selection, an optional private index, bytes on standard input, and the caller’s kept global options when a signed rebuild needs them. Why: Every replay step runs in git --git-dir=<shadow> so its objects stay in the shadow store until the next migration; one runner keeps the selection in one place.
replay_shared_paths
Replay: shared paths of the landed history and the prepared commit, with their three entries (replay_shared_paths). What: The paths both the prepared commit and the landed history changed since the preparation base, with the three tree entries subsumption compares. Why: Subsumption decides per shared path; the listing is two git diff-tree runs whatever the history length, and paths stay Latin-1 decoded so every byte survives and encodes back exactly.
replay_subsumption
Replay: subsumption rules and the synthetic merge base (replay_subsumption). What: Subsumption before replay’s three-way merge: the per-path rules, and the synthetic merge base that makes git merge-tree take the prepared bytes for every subsumed path and the landed bytes for every path capture order drops. Why: A path whose prepared bytes already contain the landed change keeps the prepared entry, matching what native sequential commits produce in a shared worktree. Setting the synthetic base to the landed content means the merge sees no landed change there.
replay_subsumption_text
Replay: the text half of subsumption over index-named blobs and hunks (replay_subsumption_text). What: The text half of replay’s subsumption: which regular-file paths hold prepared bytes that already contain the landed change. Why: A path is subsumed when the landed change applies in reverse to the prepared bytes with full context, or when the prepared change of every landed region is the landed change extended on one side. Hunks come from index-named single-level trees, so no patch header carries a user path, and binary detection is Git’s content rule (a NUL byte in the first 8000 bytes of any of the three blobs).
repo_read_adapter
In-process repository reads: the layout turned into the shapes the wrapper’s existing queries answer with. What: The shapes the wrapper’s existing queries answer with, built from an in-process RepoLayout. Why: Each caller keeps its own result type; the adapter is the one place that says how a layout becomes a RepositoryLocation, a LandingLocation or a sequencer state, so a caller tries the layout first and asks Git only when the layout declines.
repo_read_config_list
In-process repository reads: the merged configuration listing with includes and environment entries. What: The merged configuration listing of git config --list -z --show-scope, built in process from the system, global and repository files, their include.path files, and the environment’s GIT_CONFIG_COUNT entries. Why: The wrapper reads this listing once per commit and asks several single-key questions Git answers from the same files. The per-worktree config.worktree is listed under the scope worktree after the repository’s own file when extensions.worktreeConfig is on. The reader declines conditional includes (whose condition Git evaluates), command-line -c entries (GIT_CONFIG_PARAMETERS), a file it cannot parse and an environment entry it cannot canonicalize.
repo_read_config_scan
In-process repository reads: proof that no configuration scope sets core.bare or core.worktree. What: The proof, from files alone, that no configuration scope Git reads for repository discovery sets core.bare or core.worktree. Why: git rev-parse classifies a repository from the discovered directory plus those two keys of any scope; the in-process location reader may only answer when none of the files it can see mentions either key, so the check follows every include and includeIf path without evaluating its condition (a conditional include that mentions a key is enough to leave the question to Git).
repo_read_config_text
In-process repository reads: the plain-text configuration reader that gives up on any spelling it cannot prove. What: A reader of Git configuration file text that gives the entries in file order, and gives up on any spelling it cannot prove it reads as Git reads it. Why: The in-process repository reads (location, remotes, upstream) need a few configuration values; Git answers a wrong or surprising spelling with its own error, so every spelling outside the plain grammar returns None and the caller asks Git.
repo_read_config_typed
In-process repository reads: proof that every configuration value Git types at startup is one Git accepts silently. What: The proof that every configuration value Git types at startup, in every scope the readers look at, is a value Git accepts without a message. Why: Git reads many keys for every command (core.fileMode, core.compression, advice.*, diff.context, and so on) and dies (fatal: bad boolean config value 'maybe' for 'core.filemode'), or warns, when a value does not fit the key’s type. The readers never look at most of those keys, so without this proof they would answer where Git fails. The proof accepts only spellings it can show Git accepts (a conservative subset) and leaves every other spelling of a typed key to Git, which then words its own failure. The key table was built from the keys real Git 2.56.0 rejected or warned about when set to malformed values (doc/planning/cli-git-in-process-reads.md, “Typed configuration values”), and every_documented_key_with_a_malformed_value_is_declined_or_equals_git keeps it honest against the Git that runs the tests.
repo_read_diff
In-process repository reads: the difference between two sorted entry lists and the output spellings. What: The difference between two sorted lists of tree-like entries (a flattened tree, an index), in Git’s raw and name-only spellings, and the pathspec forms and filters the wrapper’s own queries use. Why: diff-index --cached, diff --cached and diff-tree print exactly these lines for the plain cases; every case where Git’s output depends on more than the two lists (a file that became a directory, a gitlink, a possible rename) returns None, so Git answers it.
repo_read_hook
In-process repository reads: the two runner entry points that try the answers before starting Git. What: The two places where the wrapper’s Git runners try the in-process answers first. Why: run_metadata_git (captured queries) and run_git (transaction requests) are the choke points every read-only Git start goes through, so one call in each gives every caller the saving and keeps the fallback to Git in one place. Setting CLI_GIT_IN_PROCESS_READS=off in the wrapper’s environment turns the answers off, for a measurement that compares both and for diagnosing a difference.
repo_read_index
In-process repository reads: the entries of an index file, versions 2 to 4. What: The entries of an index file read in process, for versions 2, 3 and 4. Why: ls-files --stage, ls-files --unmerged and the cached diffs only need each entry’s path, mode, object name and stage. The reader returns None for a split index (link), a sparse index (sdir or a directory entry), an unknown required extension, unsorted entries or a size that does not add up, so Git answers those. It does not verify the trailing checksum; Git does, and a torn index cannot occur because writers rename a finished file.
repo_read_layout
In-process repository reads: the location, Git paths and object format found from the file system. What: The repository location that git rev-parse --path-format=absolute --is-bare-repository --git-dir --git-common-dir --show-toplevel --show-prefix prints, found from the file system without a Git process. Why: Every wrapped command asks for it, and it is the only extra Git start of most forwarded commands. The reader walks up from the directory looking for .git, checks what Git checks for the plain layouts (a valid HEAD, the objects and refs directories, files ref storage, a known object format, the owner of every path, no core.bare that is true and no core.worktree in any scope), and returns None for everything else, which sends the caller to Git: a bare repository, GIT_* variables, a symbolic link, another file system, reftable, an unknown extension, a submodule that sets core.worktree. With extensions.worktreeConfig on, the worktree’s own config.worktree is read too, as Git reads it (Documentation/config/extensions.adoc; read_and_verify_repository_format in setup.c of Git 2.56.0): its core.bare overrides the common file’s, its entries are checked like the common file’s, and its includes are scanned and never interpreted.
repo_read_object
In-process repository reads: object kinds, names and the zlib step shared by loose objects and packs. What: The shapes of a Git object read in process, object names as bytes and hexadecimal, and the zlib step. Why: Loose objects and pack entries are zlib streams; every reader of this family shares one inflate call (a pure Rust decoder) and one definition of an object name, so a size or format mismatch is refused in one place.
repo_read_odb
In-process repository reads: the object database of a repository, its packs and alternates. What: The object database of one repository read in process: loose objects, packs and alternates. Why: A lookup that finds the object answers exactly what git cat-file would print for it; a lookup that does not (missing, corrupt, too large, replaced by a refs/replace ref, in a shallow or grafted history) returns None, and the caller asks Git, which words the failure or fetches a promised object itself.
repo_read_pack
In-process repository reads: one pack, its index lookup and delta resolution. What: A reader of one Git pack: the version 2 index lookup by object name and the entry read, with OFS_DELTA and REF_DELTA resolution. Why: Most objects of a real repository are packed. The reader opens the two files, seeks for the few bytes a lookup needs, and returns None for anything it does not fully understand (a version 1 index, an inconsistent size, a delta chain deeper than the limit, a bad delta), which sends the caller to Git.
repo_read_query
In-process repository reads: answers to the exact read-only command lines the wrapper issues. What: Answers, without a Git process, to the exact read-only Git command lines the wrapper itself issues: ls-files --stage, ls-files --unmerged, diff-index --cached, diff --cached --name-only, diff-tree --root -r -m and show --no-patch --format=%s. Why: Each is a start of Git for a fact two in-process readers (the index, the object database) can produce byte for byte. The reader takes the same argument list the caller would give Git and returns Git’s standard output and exit code, or None for any command line, repository or configuration it does not fully understand, so a caller tries it first and starts Git on None.
repo_read_query_refs
In-process repository reads: answers for the reference and configuration command lines. What: In-process answers for the reference and configuration command lines the wrapper issues: config --list -z --show-scope, remote get-url <name>, rev-parse --verify --quiet <ref>^{commit}, for-each-ref --format=%(refname)%00%(objectname)%00%(symref) and rev-parse --abbrev-ref --symbolic-full-name @{upstream}. Why: Each reads only the configuration files, HEAD, the loose and packed refs and at most a few objects; every case where Git’s output depends on more (a linked worktree, a conditional include, a rewritten URL, an ambiguous short name, a broken ref) is None, which starts Git.
repo_read_refs
In-process repository reads: the files ref storage, one ref by name, a symbolic chain and the list of all refs. What: The files ref storage read in process: one ref by exact name, the symbolic chain behind it, and the list of every ref. Why: A loose file wins over packed-refs; a symbolic file names another ref; both are plain text. Every spelling the reader is not sure of (a link, a name outside a plain alphabet, a damaged value, a linked worktree’s split of per-worktree and shared refs) is Unsure, which sends the caller to Git.
repo_read_tree
In-process repository reads: trees, flattened trees and commit fields. What: Trees and commits read in process: the entries of a tree, the flat list of a whole tree, and the header fields and subject of a commit. Why: The diff and listing readers compare a commit’s tree with an index or another tree path by path, and show prints a commit’s subject; both need only these parses, and a commit with a header the parser does not know (a different message encoding, a signature it must not interpret) returns None so Git answers.
repository_facts
Lazily fetched repository facts behind one interface, with the real-Git provider. What: The repository facts policies and fixed transforms need, behind one small interface, with the provider that asks real Git. Why: The rule cores decide from arguments alone and name the fact they still need. This module fetches those facts lazily and at most once: an invocation whose rules need nothing starts no Git process, and several rules that need the repository’s location share one query. Tests drive the rules with a scripted provider.
repository_location
The one query for repository identity, top level and the path below it. What: Ask real Git, in one query, which repository an invocation selects and where inside its worktree the command runs. Why: The configuration root, the require-root policy and the worktree policies all need answers Git itself computes from -C, --git-dir, --work-tree and its environment. One git rev-parse prints all of them, so an invocation starts one Git process for these facts instead of one per fact, and the wrapper never re-implements Git’s repository discovery or its -C chaining.
rule_add_explicit
Pure decision of the add-explicit policy. What: Pure decision of the add-explicit policy: git add names the paths it stages. Why: git add ., git add -A and git add -u sweep up every changed path, including ones the caller never looked at, so one commit stops describing one change. The policy rejects those forms. This file decides from the arguments alone; it reads no file and starts no process.
rule_argument_rewrite
The shared result and builder of transforms that add tokens to a command line. What: The shared result and builder of fixed transforms that add tokens to a command line. Why: A transform either leaves the caller’s arguments alone or returns a new owned list; saying which lets the caller keep the original vector without comparing contents.
rule_atomic_push
Pure decision of the atomic-push transform. What: The pure decision of the atomic-push transform: insert --atomic after git push unless the caller already chose. Why: An atomic push updates every ref on the remote or none, which prevents partially applied pushes.
rule_branch_worktree
Pure decision of the branch-worktree-only policy and its one-query remote guess. What: Pure decision of the branch-worktree-only policy: a new branch starts in its own linked worktree, not in the current one. Why: git branch <name>, git checkout -b, git switch -c and Git’s guess of a remote branch all create a branch in the current worktree. The policy rejects them and points at git worktree add -b. This file decides from the arguments alone, names the one repository query the guess needs, and reads that query’s answer. It starts no process.
rule_commit_index
Pure core of the index-against-HEAD check. What: The pure core of the index-against-HEAD check used by the commit-only transform. Why: The caller runs real Git; this module only says which command to run and what its exit status means, so the decision is testable without a process.
rule_commit_only
Pure decision of the commit-only transform. What: The pure decision of the commit-only transform: insert -o after git commit, or say why not. Why: Every commit must name the paths it includes. Two decisions need a repository fact (sequencer state, index against HEAD); this module asks for the fact by returning a Needs... variant and the caller answers through a resolve_... function, so no process or file is touched here.
rule_commit_only_message
User-facing diagnostics of the commit-only transform. What: The user-facing diagnostics of the commit-only transform. Why: Each message names the rejected form and every valid way forward; keeping the text apart from the decision keeps both files readable.
rule_commit_sequencer
Pure core of the merge, cherry-pick and revert conclusion check. What: The pure core of the “is a merge, cherry-pick or revert awaiting its commit” check. Why: The caller runs real Git and probes the filesystem; this module says which query to run, reads its output exactly, and decides from three presence facts.
rule_linked_worktree
Pure decision of the linked-worktree-only policy. What: Pure decision of the linked-worktree-only policy: commands that can discard uncommitted state never run in the main worktree, and run in a linked worktree only while it is clean. Why: git stash that changes state, a deleting git clean, a git reset that is not --soft, git restore, git checkout and git switch forms that overwrite files, a forced rm, mv, or checkout-index, read-tree --reset and an --abort can throw away work, and the loss leaves no record. This file decides from the arguments alone whether the command is guarded and when, and from the worktree kind and dirtiness the caller measured whether to reject it. It starts no process.
rule_linked_worktree_messages
The rejection texts of the linked-worktree-only policy. What: The rejection texts of the linked-worktree policy: outside any worktree, in the main worktree, and in a worktree that holds uncommitted state. Why: Each text names the command, says what it could destroy, guesses what the person was trying to do, and names a sanctioned way to do that without losing work; the deliberate way to discard anyway comes last. Every alternative named here is run through the built executable by binary_discard_tests.rs, and the README lists each form with the same alternatives.
rule_require_root
Pure decision of the require-root policy. What: Pure decision of the require-root policy: commands run from the repository root. Why: Running Git from a subdirectory makes pathspecs mean something else. The policy rejects that, except for commands that need no repository. This file decides from the argument list alone which commands are exempt, and from two paths the caller measured whether the directory is the root. It reads no file and starts no process.
rule_status_hints
Pure decision of the status-hints transform. What: The pure decision of the status-hints transform: insert -c advice.statusHints=false before git status unless the caller set that key. Why: Git’s stock status hints suggest git add <file>... and git commit -a, forms the wrapper’s policies reject; the wrapper prints accurate guidance instead.
rule_worktree_dirty
The query and reading of “does this worktree hold uncommitted state?”. What: The question “does this worktree hold uncommitted state?” as one Git query and its reading. Why: A command that can discard uncommitted state is judged by whether any exists: a clean linked worktree has nothing to lose, a dirty one does. Tracked changes (staged or not) always count; untracked files count only for the commands that delete them.
scanner_adapter
The linked forbidden-strings scanner: one load per invocation, typed redacted findings. What: The repository’s forbidden-strings scanner, linked in and loaded once per invocation. Why: The TypeScript wrapper wrote each candidate to a temporary file, started the scanner as a child process and parsed its standard error. Here candidate bytes go straight to the scanner library, and its own typed findings come back. Nothing is re-derived: rule loading, the rule cache, matching, redaction and the fail-closed findings all stay the scanner’s.
scanner_failure_code
Which engine failure code each candidate and scanner failure carries. What: Which engine failure code each failure of the candidate and scanner layers carries. Why: An engine failure names its cause. content-unavailable means candidate bytes or a repository fact could not be read; policy-incomplete means the policy’s own machinery failed. Deciding the code here, beside the failures, makes every policy that reads candidates or scans them report the same code for the same cause.
scanner_run
One scan pass over a candidate version’s exact bytes. What: Scan every eligible candidate of one version through the loaded scanner. Why: This is the whole forbidden-strings check of one policy pass: candidate bytes come from the candidate store (staged or committed, never the worktree), go to the scanner in memory, and the scanner’s own results come back. No temporary file is written and no process is started per file.
scanner_selection
Rules-file precedence and which candidates the scanner is given. What: Which rules files the scanner loads, and which candidates it is given. Why: The scanner library filters nothing; the standalone scanner and the TypeScript policy applied these choices around it. They are restated here once, as pure functions: the rules-file precedence (the configured rulesFile entries, then the FORBIDDEN_STRINGS_RULES list, else forbidden-strings.local.txt in the repository root) and the paths that hold the rules themselves, which would otherwise match their own content.
scratch_location
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.
sequencer_marker
Proof, without a Git process, that a sequencer marker names nothing on the files ref storage. What: Proof, without a Git process, that a sequencer marker such as MERGE_HEAD names nothing. Why: A commit asks git rev-parse --verify --quiet <marker> for each of three markers, and in almost every commit none exists. On the files ref storage that question reads a fixed list of places, so when every one of them is empty the answer is “absent” without a process; any entry in any place, of any kind or content, says “maybe present” and the caller asks Git, which alone decides whether the entry resolves (a dangling marker still counts as a conclusion).
service_amend
What an amend takes from the commit it replaces: parents, message and author. What: The facts of the commit an amend replaces, read once from the repository: its parents, its first parent’s tree, its author and its message, and the author line an amend makes of them. Why: An amend keeps the parents of the commit it replaces, keeps its message when no message option is given and keeps its author unless the command line says otherwise. All of that is arithmetic on the replaced commit’s own record, so the wrapper embeds it; each rule is compared field by field with native git commit --amend in binary_service_amend_tests.rs. A commit with a header the amend would have to carry (another encoding, a merge tag) returns nothing and native Git handles the line.
service_captures
The service’s memory of when the bytes of the commits it landed were captured, per worktree. What: The service’s memory of when the bytes of the commits it landed were captured, kept per worktree. Why: Two commands that capture a file from one worktree at different moments hold different states of it, and the later capture holds everything the earlier one did (doc/decision/cli-git-concurrent-commits.md, “Serial landing”). When the service sends a commit back because a landed commit changed the same path, the client needs to know which of the two captured later, and only the service saw the landed commit’s request. The stamp is a reading of CLOCK_MONOTONIC, one clock for every process of the host, so captures made by different wrapper processes compare. The memory lives in the service process and starts empty with it, so a restart can only make a capture unknown, never misordered: a commit with no entry is treated as captured by somebody else, and the client then merges instead of preferring either side. A reboot restarts both the clock and the service together. Entries are kept per worktree, because the rule holds for captures of one worktree only.
service_claim_renewal
The renewal that keeps the claims of a shared work directory from looking abandoned while their holder works. What: Keeps the lease of the claims one service instance holds fresh while it works, so a claim is taken over only when its holder stopped, never because its holder is slow, and tells the holder when its claim was taken over anyway. Why: A claim’s lease starts at the clock reading in its file name and the other instances run a claim again once the lease ran out. A push can take longer than any fixed lease, and an item can wait behind other claimed work; a claim that is never renewed would make both look abandoned. A holder that dies stops renewing, so its claims still expire, and no lock file is involved: the claim’s file name is the whole protocol. A renewal renames the claim from the name the holder last wrote, so it can only reach its own claim, and a claim that was taken over is found gone instead of being renewed or settled on another instance’s behalf.
service_client
The wrapper’s side of a commit through the commit service. What: The wrapper’s side of a commit through the resident service: connect, wait honestly while the operating system starts the service, send the request, show the queue position, survive a service that dies mid-request, and turn every failure into the plain text of service_messages.rs. Why: A failure the system can resolve is resolved here without the user: a service that is starting is waited for, one that crashed is reconnected to with the same request identity, one that was started from another release of the wrapper (older or newer, found by the release its welcome names) is asked to retire and is reached again once the manager has started the current one. Only what cannot be resolved ends the command.
service_commit
The commit command’s path through the commit service. What: The commit command’s path through the resident service: prepare the commit in this process (private index, policies), send the prepared change to the service, and report what it landed. Why: The service is the only writer, so a commit takes no lock and polls nothing: preparation and policies run here, outside the service’s serialized step, and the service moves the branch once for a whole batch of commits (doc/planning/cli-git-resident-service.md, “Where preparation and policies run”). Every command line lands through it. The lines service_commit_args.rs accepts are built here from the command line (message and identity options, sign-off, trailers, a reused or fixup message, -a, an index commit, --amend); every other line, and every commit that is not on an ordinary branch with an ordinary state, is built by real Git in a scratch repository and landed as Git made it. Which route a commit takes is decided by service_route.rs; what is prepared is service_prepare.rs.
service_commit_args
Which commit command lines the commit service covers, and the message they stand for. What: Which git commit command lines the resident service lands, and the message such a line stands for. Why: The service lands the scoped commit an agent or a person types most (git commit -m <message> -- <paths>), with the options that only shape the message or the identity: -F <file>, --author, --date, --trailer, --cleanup, --allow-empty-message, -s, --amend and --reset-author. Every other option (editor, signing, templates) still goes through the transaction until the service covers it, so this module answers “is this line one the service covers” before anything is prepared, and builds the message with the clean-up Git applies when no editor opens.
service_fault
Test-only fault points of the commit service. What: Test-only fault points of the resident service: CLI_GIT_TEST_ONLY_SERVICE_FAULT=<point> ends the service with SIGKILL the moment it reaches the named point of a batch. Why: The kill tests need a deterministic instant, as the transaction phase markers give the wrapper (transaction_phase.rs). The marker is compiled into the release executable, and only the explicit variable arms it.
service_handoff
The wrapper’s side of after-commit work: records and the wake. What: The wrapper’s side of after-commit work: leave the push and upkeep records for a commit the service landed and wake the service. Why: The commit returns as soon as it is saved. The push (which can take as long as the network does) and the upkeep run in the service afterwards; a push that fails is reported by the person’s next wrapper command (push_failure_notice.rs) (doc/planning/cli-git-resident-service.md, “After-commit work”). The wrapper starts no program and waits for no one: it writes whole files and sends one message.
service_head
Whether a worktree’s head still names the branch a commit was prepared on. What: Whether a worktree’s head still names the branch a commit was prepared on. Why: Native git commit updates HEAD, so a worktree that switched branches while the commit waited (in the editor, for a signature) fails with “cannot lock ref ‘HEAD’” and changes nothing. The service moves the named branch itself, so it asks the same question before anything moves: a branch moved under a worktree that is on another branch would leave that worktree’s index describing the wrong commit. The answer is read from the worktree’s HEAD file when that file can be trusted and from Git otherwise, so an ordinary landing starts no extra Git.
service_hold_retry
A forwarded index writer started again when the commit service took the index before its Git did. What: Runs a forwarded index writer again when real Git lost the race for the index to the commit service, instead of showing the person Git’s refusal. Why: The wait for the service’s hold ends when the hold is released, but the service may take the index again for the next landing before the writer’s own Git has started (a burst of commits lands one batch after another), and Git then ends with its refusal to create the index lock. For the few commands that take that lock before they change anything and read no standard input, running them again once the service lets go is the same command the person typed. The refusal is kept in a file while this can happen and is shown only when it is final, so a transient loss is never printed. Every other command, and a lock that a foreign program holds, keep the behavior of native Git.
service_index_attempts
The attempts and the wait of an acquisition of the index lock. What: The attempts and the wait of an acquisition of index.lock: placement tried again after each wake, a dead service’s lock reclaimed at once, a live or foreign holder waited for on the watch, and the notices that name the holder. Why: The wait blocks on an inotify watch set before the first attempt, so a release between an attempt and the wait is not missed; the only timers are the limit and the repetition of the holder’s line. The module is split from service_index_lock.rs, which holds the acquisition’s entry points and the lock itself.
service_index_found
What an acquisition of the index lock found when it had to wait, for the service’s log. What: What an acquisition of the index lock found when it had to wait: the marker on the lock, how old the lock is, whether the holder it names is gone, running or unknown, and which processes have the lock open; the notes an acquisition keeps for the service’s log. Why: A commit that waited five seconds for the index lock left a log that said how long and not for whom. The description is built only after a placement found the lock held, so the fast path of an acquisition pays nothing for it; the scan of /proc/*/fd for the processes that have the lock open is made once, at the first look.
service_index_hold
The service’s hold on a placed index lock: the check that it is still the holder’s, the release and the removal. What: The service’s hold on one index lock after it is placed: the check that the lock is still the holder’s, the release that installs the new index, and the removal of a lock that is not released. Why: A holder removes its lock by path, so it first asks whether the path still names the file it placed (service_index_ownership.rs). A question that cannot be answered is neither answer: the lock stays, the failure is said in the log, and the service ends after its batch so that the restart reclaims the lock. The module is split from service_index_lock.rs, which re-exports IndexLock.
service_index_identity
Who a marker’s holder is: the identity a process writes, and the answers a reader gives (gone, running, unknown). What: Who a marker’s holder is: the identity this process writes into a marker, and the three answers a reader gives about a holder named in one: gone, running, or unknown. Why: A marker names a process by its PID and start tick, which mean something only in the boot and the PID namespace they were read in. The identity therefore ends in the boot ID and the PID namespace, a writer whose /proc numbers processes differently from the process itself writes none, and a reader that cannot compare like with like answers unknown, which keeps the lock and proves nothing, instead of reading a number of another namespace as a local process and calling a live service gone. The module is split from service_index_marker.rs, which re-exports the public items here under their old names.
service_index_lock
The commit service’s hold on Git’s index lock across a landing. What: The service’s hold on Git’s own index.lock across a landing: taken first, by exclusive creation, waited for without polling when another program holds it, and released by moving the new index over the real one. Why: Git’s own commit takes the index lock before it moves the branch and gives it up after the index is written. A landing that moved the branch first and took the lock afterwards left the index showing content a landed commit had replaced for as long as a foreign git held the lock, and a commit prepared from that index could land a revert. The wait for a foreign holder is event-driven: an inotify watch on the Git directory wakes the service when the lock itself is removed or moved away, and the only timer is the limit (CLI_GIT_LOCK_WAIT_SECONDS) and the repetition of the holder’s notice. On platforms without inotify a held lock fails at once with the plain message. The lock carries a marker with the batch number and the holding process from the moment it exists until it is removed after the new index is in place, so a restart can tell its own stale lock from a foreign one wherever the service was killed, and never takes over a lock whose holder still runs (doc/planning/cli-git-resident-service.md, “Landing every command line”). The marker, the inotify watch, the placement of the marked file, and the attempts and the wait are in service_index_marker.rs, service_index_watch.rs, service_index_placement.rs and service_index_attempts.rs, and are re-exported here under the names callers use.
service_index_marker
The marker the service writes into the index lock and the questions asked of a marker. What: The marker the service writes into the index.lock it holds, and the questions asked of a marker found there. Why: A restart tells its own stale lock from a foreign one, and a live holder from a dead one, by the marker alone: the batch, the holding process and that process’s birth identity. The module is split from service_index_lock.rs, which re-exports the public items here under their old names.
service_index_ownership
Whether the index lock is still the file the holder placed, and the record of a lock that could not be settled. What: Whether the file at the path of an index lock is still the file this holder placed there, and the record that a lock could not be settled. Why: A holder releases or removes its lock by path. The staging file’s descriptor is kept open for as long as the hold lasts, so the inode it names cannot be freed and numbered again, and asking whether the path still names it needs a stat and a pread, never a new descriptor. A check that opened the lock by path failed with EMFILE exactly when the service was out of descriptors, and a failed check was read as “another program’s lock”, which left a lock naming the live service behind. A question that cannot be answered is neither answer: the lock is kept and the service ends after its batch, so that the restart reclaims it.
service_index_placement
How the service’s marked file becomes the index lock, and the cleanup of staging files. What: How the service puts its marked file in place as index.lock: the system calls it makes, the atomic placement itself, and the removal of a stale lock. Why: The lock must never exist without its marker, so the marker is written to a file that is new and then made the lock in one step (a rename that does not replace, else a hard link). The calls are one Platform value so a control can refuse or count them for one acquisition alone. The module is split from service_index_lock.rs, which re-exports the public items here under their old names; the staging files are in service_index_staging.rs.
service_index_prestage
The marker of the next batch’s index lock, made and flushed while the service is idle. What: The marker of the next batch’s index lock, created, written and flushed to storage while the service is idle, so that a commit’s lock path only places it. Why: The marker must be on storage before it can become index.lock, so that a power loss never leaves a lock with an empty or partial marker. That flush is a few milliseconds on an idle disk and seconds on a loaded one, and it sat on every commit’s path. The batch number of the next landing is known when the last one ends (one more), and so is the index, so the file is made then. One file at most exists for the service, it is kept in this thread’s slot and not in a file other processes could take for their own, it is removed when the service ends or the next batch is not the one it was made for, and it names this process, so the next service removes what a crash left (remove_dead_staging). A commit that arrives while the flush is still running waits for it and does not start a second.
service_index_staging
The staging files of the index lock. What: The staging files of an index lock: their names, their creation with the marker written, and the removal of the ones a killed attempt left. Why: The marker is written to a file that is new and becomes the lock in one step, so the lock never exists without it. A staging name carries the process and a token no other attempt has, so no attempt opens a file another left, and a later attempt tells from the name alone whether the writer still runs. The module is split from service_index_placement.rs.
service_index_wait
The wait of a wrapped index writer for the commit service’s own hold on the index. What: The wait of a wrapped command that writes the index for the commit service’s own hold on the index: event-driven, with a notice that names what is waited for, and over as soon as the hold is released. Why: The service holds Git’s index.lock while it lands a commit and marks it as its own. A wrapped git add run by another process in the same worktree during that time must not fail, and must not be told the hold belongs to a program nobody can prove alive: the hold is the product’s own and ends by itself. A lock without the service’s mark is a foreign program’s and keeps the behavior of native Git. The wait blocks on an inotify watch set before the first look, so a release between the look and the wait is not missed, and it ends at the limit every wait on a live program ends at.
service_index_watch
The inotify watch the wait for the index lock blocks on. What: The inotify watch on the directory of an index lock, and the reading of the events it delivers. Why: The service waits for a holder of index.lock without polling: the watch wakes it when the lock is removed or moved away, when the kernel reports an overflow that may have hidden that, or when the watch itself ended (the directory was unmounted or removed). Platforms without inotify have no watch, and a held lock fails there at once with the plain message. The module is split from service_index_lock.rs, which re-exports everything here under its old names.
service_instance
Which commit service instance belongs to a worktree: its name, socket and unit instances. What: Which commit service instance belongs to a worktree: a name made from the worktree’s own Git directory, and the socket, the unit instances and the journal directory that name gives. Why: One service instance runs per worktree, the way Bazel runs one server per workspace, so a wait in one worktree (a foreign holder of its index lock, a slow push) never delays another’s commits. A worktree’s own Git directory (the main worktree’s .git, or .git/worktrees/<id> of a linked one) holds its HEAD, its index and its work records, and Git allows one branch to be checked out in one worktree only, so the instances never write the same branch (doc/planning/cli-git-resident-service.md, “One service per worktree”). The name is a hash because an escaped path does not fit a unit name for an ordinary home directory, and the hash is the same in the wrapper and in the service, which are built together. A collision of two worktrees would only make one instance serve both, never land anywhere wrong.
service_journal
The commit service’s journal and its restart decisions. What: The resident service’s journal: one file per batch of commits, with the one durable write the batch needs, and the decision a restart makes about a batch it finds open. Why: The branch moves only after the intent is on disk, so after any kill the journal and the branch say whether the batch landed; a restart finishes the one step that may be missing (the index update) or drops the intent, and a request that landed is remembered by its identity so a retry is answered, not landed twice (doc/planning/cli-git-resident-service.md, “Durability and crash recovery”).
service_land
The commit service’s serialized step. What: The service’s serialized step: land a batch of prepared commits on a branch, and finish the batches a killed service left open. Why: Everything that must happen one commit at a time happens here, in a fixed order, with one durable write: the objects of the whole batch are written by one fast-import, the intent is journaled, the branch moves by one compare-and-swap, the index entries of the named paths are updated, and the intent is marked done (doc/planning/cli-git-resident-service.md, “The serialized step, group commit, FIFO and backpressure”).
service_land_follow
What the commit service’s landing does once the branch has moved: index entries and conclusion state. What: What the service’s landing does once the branch has moved: the worktree’s index entries of the commit’s paths and the conclusion state a merge, cherry-pick or revert leaves behind. Why: A commit Git built in a scratch repository carries no list of changes, and a conclusion’s state files belong to the commit that concludes it. Both steps are idempotent, so the same code finishes a batch a killed service left open. The index entries come from the repository itself: the paths that differ between the branch before and the commit, as bytes, handed to reset on its standard input, so a name that is not UTF-8, a long list and a gitlink need no special case (doc/planning/cli-git-resident-service.md, “Landing every command line”).
service_message
The message and identities of a service commit, made as native git commit makes them. What: The message and the identities of a commit the service lands, made from the command line the way native git commit makes them when no editor opens. Why: Everything here is text arithmetic that Git itself defines (clean-up, sign-off, trailers, the subject of a fixup, the message and authorship of a reused commit), so the wrapper embeds it instead of starting git commit; each rule is compared field by field with the native commit in binary_service_options_tests.rs. Whatever cannot be made without guessing returns nothing and native git commit handles the line.
service_messages
What the user is told when the commit service cannot land a commit. What: Everything the wrapper tells the user when it cannot get a commit landed by the resident service, and the notices it prints while it waits. Why: A failure the system can resolve is resolved without the user; the rest ends the command with plain words, the affected input named, every remedy given, and no implementation term (doc/planning/cli-git-resident-service.md, “Failures the client can meet”). The texts live here as constants so a test can check each against a list of terms that must never reach a user, and so the text a client prints cannot drift from the text the tests read.
service_native
The commits real Git builds for the service: an editor, a signature, and what the service does not embed. What: The commits real Git builds for the commit service: an editor, a signature, and every command line the service does not build itself. Why: Git owns the editor, the signer and the message semantics of whatever the service cannot embed, so this module points real git commit at a thin scratch repository (its HEAD and branch at the base, the policy-settled private index, the real configuration included, every hook off) whose new objects go straight into the real object store. Nothing is locked and nothing is registered: the scratch repository sits inside the commit’s own scratch directory, is removed with it, and the commit it makes is only an object until the service moves the branch to it by compare-and-swap (doc/planning/cli-git-resident-service.md, “Commits Git builds”).
service_prepare
The preparation of a commit that the service lands, for every command line. What: The preparation of a commit that goes through the resident service: the private index, the selection UI, the policies, the settled changes, and the facts the landing needs, for every command line. Why: The service lands what is prepared here, so the preparation covers every shape of commit: a branch, a detached head or a branch that does not exist yet, an ordinary commit or the conclusion of a merge, a cherry-pick or a revert, a selection by path, by --include, by --patch/--interactive or by a pathspec file, and a policy that rewrote a file. Whatever Git alone can answer (an unmerged index, nothing to commit) is marked for Git to answer in the scratch repository, so its words and its exit status are the person’s. Nothing here locks anything, and nothing is registered (doc/planning/cli-git-resident-service.md, “Landing every command line”).
service_protocol
Messages between the wrapper and the resident commit service. What: The messages between the wrapper and the resident commit service, one JSON object per line. Why: A client sends a prepared change with a request identity and waits; the service answers with a landing, a queue position, a request to prepare again, or a failure from a closed list. The line form needs no framing library and every field is checked on read, so a damaged or hostile line is refused rather than guessed (doc/planning/cli-git-resident-service.md, “Protocol and request identity”).
service_provision
The wrapper’s own provisioning of the commit service’s socket activation. What: The wrapper makes socket activation for a worktree’s commit service exist the first time a commit needs it: it writes the two template unit files into the user’s runtime unit directory, asks the user manager (through the session bus) to reload and to start the worktree’s socket instance, and returns. The manager, not the wrapper, starts the service when the first connection arrives. Why: There is no install step to remember (doc/planning/cli-git-resident-service.md, “Provisioning”): like Bazel’s client, the wrapper sets up what it needs and tells nobody. One service runs per worktree, so the units are templates (cli-git@.socket, cli-git@.service) and each worktree’s instance is named by a hash of its own Git directory (service_instance.rs). The units live under $XDG_RUNTIME_DIR/systemd/user, which disappears at logout, so nothing is left behind in the home directory and the next first use provisions again. Every step is idempotent (a file is written, by temporary file and rename, only when its content differs), so several commits racing to provision agree without any lock. Every commit that goes to the service compares the two installed templates with what this wrapper would write (two small reads) before it touches the bus, so an upgrade or a downgrade, which moves the service program the template names, is repaired by the first commit that follows it: templates rewritten, manager reloaded, failed state cleared, the worktree’s socket started. The single service of the earlier releases (cli-git.socket and cli-git.service, one socket for every worktree) is asked to retire and stopped, and its units and socket file are removed, by the first commit of the new release; its open batches are finished by the instance of the worktree they belong to, and the work it had claimed is claimed again by the next drain. Units this wrapper’s earliest releases wrote into ~/.config/systemd/user outrank the runtime ones in the unit search path, so they are recognized by their content and removed; a unit of the same name that something else wrote is never touched, and the commit stops with a message naming the file.
service_pruned
How a commit headed for the service notices that its prepared content was removed before it landed. What: How a commit headed for the service notices that the content it prepared was removed from the repository before it landed: the policy read that fails on a missing object, the check of the request’s objects after the service could not land it, and the numbered phase markers a test uses to remove content at a chosen instant. Why: A commit’s blobs, trees and commit object are unreferenced until the branch moves, and git gc --prune=now deletes every unreferenced object at once. The Git manual for git gc (the installed 2.56.0 manual and https://git-scm.com/docs/git-gc, section NOTES, checked 2026-11-01) says: “when git gc runs concurrently with another process, there is a risk of it deleting an object that the other process is using but hasn’t created a reference to. This may just cause the other process to fail or may corrupt the repository if the other process later adds a reference to the deleted object”, and for the option itself: “–prune=now prunes loose objects regardless of their age and increases the risk of corruption if another process is writing to the repository concurrently”. Git’s own mitigation is the two-week grace of the default --prune date, which --prune=now removes. This commit path therefore says plainly what happened and what to do (ClientFailure::ContentPruned), instead of showing a policy event for a machine or a failure of the service. It does not prepare the commit again: the bytes the command captured are what is gone, and reading the worktree again would commit whatever it holds by then.
service_replay
The retry of a commit the service sent back: the captured change merged onto the branch, never prepared again from the worktree. What: The retry of a commit the service sent back because a path it names changed on the branch since its base: the change the first preparation captured is merged onto the branch as it is now, and the worktree is not read again. Why: A commit lands the bytes that were there when the person ran the command (doc/decision/cli-git-concurrent-commits.md, “Serial landing”; SPEC.md: “Cli-git never re-prepares a commit from current worktree bytes”). Preparing again from the worktree landed bytes written after the command, and bytes another command meant. The merge base is the preparation base, and a path the other commits changed that the captured change already contains is kept as captured (the same subsumption rule the earlier landing path applies, replay_subsumption.rs). The first merge is one Git run; the subsumption runs only for the paths that conflicted, so a retry that merges cleanly costs three runs here: write-tree for the captured tree, merge-tree, and read-tree of the merged tree into the private index.
service_retry
The repositories the service looks at again when a claim another instance holds may have run out. What: The repositories the service must look at again, and when: those with a claim that another instance holds and that may run out its lease while this service sits idle. Why: A restarted service that finds the claims of the instance it replaces cannot take them before their lease is over, and nothing wakes it when the lease is: the next commit might be hours away, and the work would sit claimed by a holder that died. The work loop asks this schedule how long to wait for a wake before it looks again.
service_review
The service’s handler for a review work item, bounded by a call deadline and a total budget. What: The service’s handler for a review work item: read the landed commit, ask the decision gateway, and append the entry to the repository’s live shadow log, all inside a fixed time budget. Why: This replaces the repository’s post-commit hook that ran mise run shadow-review (doc/runbook/standards-review-gate.md). The review shares the service’s work thread with pushes and upkeep, so every step is bounded: the gateway call by a deadline, the Git reads and the write by the budget around the whole run. A review that fails leaves its item in failed as a plain record and the log shows the gateway’s failure as an entry; nothing is ever left claimed.
service_review_key
The standards-review credential: redacted when printed, read from the wrapper’s environment, sent in the work message. What: The gateway credential of the standards review as it travels from the wrapper to the service: a value that prints as redacted, is read from the wrapper’s own environment, and is carried only in the in-memory work message over the user-only socket. Why: The credential must not reach the on-disk work item, a unit file or the manager’s environment, and must not appear in a log line by accident. A type whose Debug output hides the value keeps it out of every {:?} the code or a test prints; reading it takes the explicit expose. Nothing here depends on the network client, so the wrapper’s client path may use it.
service_route
Which way a commit goes: through the service, set up first, or the earlier path. What: How a commit reaches the resident service: already listening, or set up first by the wrapper. Why: There is no switch and no install state for the person to manage (doc/planning/cli-git-resident-service.md, “Provisioning”). The route follows the machine: a user session bus and a service program next to the wrapper mean the wrapper can set the service up itself, and keeps it set up (the socket is the manager’s and outlives every upgrade, so its presence never says the installed units name this release’s program); a socket without them is used as it is; anything else has no route, and the commit stops with the plain message of its platform, because the service is the only writer of a commit. The decision reads the file system only and starts no process.
service_server
The commit service’s loop. What: The resident service’s loop: accept connections, queue requests in arrival order, land what is queued as a batch, answer each client, and exit when told to retire. Why: One writer lands every commit, so there is no lock to take, hold or wait for: order is the order of the queue, the cost of the one durable write is shared by the batch, and a client that waits blocks on its socket instead of polling (doc/planning/cli-git-resident-service.md). The service never starts itself: the operating system hands it the listening socket (socket activation), or a development run binds one.
service_signal
The line the commit service writes when a signal ends it. What: The service’s last line when a signal ends it: the service manager stops a unit with SIGTERM, and a terminal sends SIGINT or SIGHUP; the handler writes one line with the signal and the uptime, then lets the signal end the process as it would have without a handler. Why: The journal of the unit with the 17-second commit had no line for the first service’s end, and the manager’s own lines about the unit cannot be relied on. A signal gives the process no chance to log from ordinary code, so the line is built and written with nothing but calls that are safe in a handler (clock_gettime, write, signal, raise).
service_timing
The lines the commit service writes about its life and about the time each request took. What: The lines the resident commit service writes to its log (standard error, which the service manager keeps in the journal) about its own life and about the time each request took: when it started, one line for each request it answered with the phases of the wait, a retire request, and its exit with the reason and the uptime. Why: After a slow commit the journal held ready and nothing else, so nothing said whether the time went to the queue, to another program’s hold on the index, to the landing or to a service being replaced. These lines cost one clock read per phase edge and one line per request; they name no person (the commit’s message and identities never appear) and are not shown to the person at the terminal.
service_work
The service’s consumer of after-commit push and upkeep work. What: The service’s consumer of after-commit work: pending push and maintenance work items of one repository are claimed, coalesced, run, settled, and a push that failed leaves a record that the person’s next wrapper command shows. Why: A commit must not wait for the network or for upkeep, so the wrapper leaves a record and the resident service does the work on its own thread (doc/planning/cli-git-resident-service.md, “After-commit work”). The service is the only pusher of the repositories it serves, so no lock is needed: several pending pushes of one branch become one push, and a service that died mid-push is noticed by the lease of its claims running out, never by a process id: the holder renews the lease while it works, so a successor that starts beside a holder that is still running leaves its claims alone, and one that starts after the holder died takes them back as soon as the lease is over. Other kinds of work (the standards review) are never claimed here.
shadow_config
The shadow repository config file and the copied repository format. What: The shadow repository’s config file, written by cli-git itself, and the repository format facts it copies from the real common config. Why: Git does not apply core.repositoryformatversion and extensions.* from an included file, so they are copied explicitly. The shadow names no hooks directory (traditional hooks are unsupported, and every shadow command passes its own core.hooksPath); core.worktree, gc.auto and maintenance.auto follow the include, so the real config cannot redirect the worktree or start maintenance in the shadow; hook.<event>.enabled = false for every hook event follows it too, because the wrapper supports no external hook (src/shadow-repository/shadow-config.ts).
shadow_create
Creation of the shadow repository of one transaction. What: Creates the shadow repository of one published transaction: an empty bare repository, its config and alternates, the linked shared entries, the conclusion state, and the ref snapshot. Why: The shadow is a separate repository rather than a registered worktree, so git worktree list never shows it and preparation creates no ref in the real repository. Its object store receives the objects native git commit writes, and its alternates name the real object directory, so a real git prune cannot delete a pending commit (src/shadow-repository/shadow-repository.ts).
shadow_git
Git commands run against a transaction’s shadow repository. What: Git commands run against a transaction’s shadow repository, and its location and removal. Why: The shadow lives at <git-common-dir>/cli-git/shadow/<transaction-id>, derived from the transaction ID so recovery finds it before prepared.json exists. cli-git’s own shadow commands remove every variable that would redirect Git elsewhere and point hooks at an absent directory, so no hook observes private shadow writes (src/shadow-repository/shadow-refs.ts, runShadowGit).
shadow_links
The shadow repository links to the real shared entries. What: Links every non-private entry of the real common Git directory into a shadow repository, and copies the per-worktree entries (config.worktree, a linked worktree’s sparse checkout). Why: Shared entries such as hooks, info, rr-cache, lfs and modules must be the real ones, so hooks and native Git see them; transaction-private entries (refs, index, objects, conclusion state, lock files) stay the shadow’s own (src/shadow-repository/shadow-links.ts). On Windows the incumbent uses junctions and copies; this port uses directory symbolic links and copies, because the standard library has no junction API.
shadow_refs
The shadow ref store: snapshot, private target, symbolic refs and HEAD. What: The shadow ref store: a snapshot of every real ref, the private target ref, symbolic refs, and the private HEAD. Why: Hooks resolve tags, other branches and remote-tracking refs as they stood at invocation. With the files backend the snapshot is one packed-refs file and the private target is a loose ref, which takes precedence over a packed entry; with reftable the snapshot is one update-ref --stdin transaction (src/shadow-repository/shadow-refs.ts).
shadow_skeleton
The entries of an empty bare repository, created in-process instead of by git init. What: The files and directories of an empty bare repository on the files ref backend, created in-process. Why: The shadow repository used to be made by git init --bare --template=, a Git start whose whole effect is these few entries; create_shadow_repository overwrites the config it writes and every later shadow step sets the HEAD it writes, so nothing else of Git’s work is used. Creating them here removes one process start from every commit (init; the config read that follows it is the wrapper’s own and stays). A torn creation leaves a directory that is not a repository; recovery removes the shadow tree as a whole (binary_recovery_tearing_shadow_tests.rs), exactly as it does for a torn git init.
shell_quote
Quoting of bytes printed inside a shell command for a person to run. What: Quoting of bytes that are printed inside a shell command for a person to copy and run. Why: A path or a kernel argument can hold spaces, quotes, ;, $(...) or bytes that are not UTF-8. Printed bare, a pasted command runs whatever follows the separator or substitution, or passes the wrong arguments. A value of safe characters is printed as it is; any other value is wrapped in single quotes (each single quote inside closed, escaped and opened again); a value with a control character or a byte that is not UTF-8 is spelled in the ANSI-C form $'...' of bash, zsh and ksh with those bytes written as \xHH escapes, so the printed text holds no control character and no line break. The same rules as package/wg/cli-quicker/src/shell-quote.ts.
signal_relay
Catching SIGINT, SIGTERM, SIGHUP and SIGQUIT while a Git child runs, and relaying only process-sent signals. What: Catching SIGINT, SIGTERM, SIGHUP and SIGQUIT while the wrapper waits for a Git child, relaying a signal to that child only when a process sent it, and reporting how the child ended. SignalGuard owns the handlers for its lifetime and restores the previous dispositions on drop. Why: A wrapper that dies on a signal leaves its commit transaction for recovery and Git running; one that catches the signal can end Git, release its locks and remove the transaction first. A signal from the terminal already reached Git as part of the foreground process group, so relaying it again would deliver it twice.
stacked_merge_push
The push of a merge that integrated a lost race, planned and run inside the running wrapper. What: The push of a merge that integrated a lost race, planned and run inside the running wrapper. Why: The merge push must pass every gate a typed git push passes, so the policies read the integrated result. An earlier version started the wrapper’s own executable with a marker variable to get those gates; the wrapper never starts itself, so the same planning (plan_invocation) now runs in this process, with the marker set only in the environment handed to the planner, never in the process environment, and the planned action is run here. The marker stops that planned push from stacking again.
standards_review_append
The standards-review shadow run: appending an entry to the shared log without a lock, once per commit, with repair. What: How the service adds a review entry to the repository’s live shadow log, which every worktree’s service instance appends to at the same time: one whole-entry write on an append-mode descriptor, once per commit, creating the directory and the file when missing. Why: The live log is an untracked file in the common repository directory (cli-git/standards-review-shadow.md), and no lock may be taken. One write of the whole entry on an O_APPEND descriptor lands at the end of the file in one step, so two instances (threads or processes, each with its own descriptor) cannot interleave their entries. Nothing else edits this file, so the protection a tracked, session-edited log needed is gone: the per-worktree spool of delivered entries, the bounded repair that appended again an entry another writer’s whole-file rewrite had lost, and the rule that the service never creates the log. A write the operating system cuts short (a signal, a full disk) is continued with the rest of the same entry, so an entry is whole unless the disk refuses it.
standards_review_client
The standards-review shadow run: the in-process HTTPS call to the decisions gateway, with a total deadline. What: The one HTTPS call of the standards-review shadow run, made in-process with a total deadline. Why: The root mise.toml task shadow-review calls fetch with a 30 s signal. This port calls the same endpoint from the wrapper itself, so no node or mise is started. The deadline covers connecting, TLS, sending and reading the body, so the call can never hold a caller longer than the deadline. The trust roots are a parameter: production uses the roots embedded in the binary, a test passes the fixture authority of its local TLS server.
standards_review_entry
The standards-review shadow run: log entry rendering and appending. What: The log entries of the standards-review shadow run, rendered as the root mise.toml task shadow-review writes them. Why: doc/audit/standards-review-gate-shadow.md already holds that task’s entries, and readers and scorers parse them, so the port writes the same lines; standards_review_append.rs appends each entry, whole, to the live log.
standards_review_input
The standards-review shadow run: which commits get an entry, and the capped model input. What: The pure part of the standards-review shadow run: which landed commits get an entry, and the capped model input built from the commit’s patch and the excerpts of its changed code files. Why: This is the root mise.toml task shadow-review after its Git reads: the no-entry, no-code-files and review cases, the 2000-line patch cap, the eight-file and 4000-line excerpt caps, and the 24000-unit input cap. Taking the patch and the file contents as plain values lets the caps be tested without Git, a network or a clock.
standards_review_patch
The standards-review shadow run: a file’s changed old-side ranges read from the patch. What: Reading one file’s changed new-side line spans out of a git show --patch text, so the review needs no git diff -U0 start per excerpted file. Why: The script runs git diff -U0 <sha>^ <sha> -- <name> for each of up to eight files. A hunk of the three-line-context patch is a list of change runs, and each run is one zero-context hunk, so the same spans follow from the patch the review already holds. The spans are on the new side because the excerpt is cut from the new-side file.
standards_review_request
The standards-review shadow run: the Decisions API request body and the answer reading. What: The Decisions API request body the standards-review shadow run sends, and the reading of the gateway’s answer into the verdict and the Findings kept line. Why: The root mise.toml task shadow-review builds this body with JSON.stringify and reads the answer with JSON.parse; the body must keep its key order and escaping so the same input sends the same bytes, and the answer text must print numbers and failures the way the log’s readers already know.
standards_review_run
The standards-review shadow run, end to end, and its bounded runner. Nothing calls it yet. What: The standards-review shadow run, end to end and in-process: classify the landed commit’s names, read the commit, build the capped input, call the gateway, and append the one log entry. Also a bounded runner that gives a caller a hard time limit over all of it. Why: This replaces the root mise.toml task shadow-review (its Luna route; the SHADOW_REVIEW_ALLOW=1 route starts pi and is not ported). The review must never delay or fail a landing, so nothing here panics or returns a failure the landing would act on, and run_within abandons the work when the time is up. Nothing calls this yet.
standards_review_source
The standards-review shadow run: the two real-Git reads (patch and file texts) of a landed commit. What: The Git reads of the standards-review shadow run, in as few real-Git starts as the data allows: one git show for the patch and one git cat-file --batch for the new-side text of the excerpted code files. Why: The root mise.toml task shadow-review starts Git twenty-odd times per landing (rev-parse, log, show, diff-tree, then show and diff -U0 for each of up to eight files). The landing already knows the commit name, its message and the changed names, so the subject and the names need no start, the zero-context ranges come out of the patch, and the file texts come in one batch. A commit with no code files costs no start at all.
standards_review_text
The standards-review shadow run: JavaScript text behaviors the log depends on (UTF-16 lengths, number text). What: The few text behaviors of the standards-review log that JavaScript defines and Rust does not share: UTF-16 lengths and prefixes, Number to text, toFixed(1) of a duration, and Git’s %s subject. Why: The root mise.toml task shadow-review measures and cuts text in UTF-16 code units and prints numbers the way ECMAScript does, and its log is compared entry by entry with this port’s, so the same text must give the same counts and the same bytes.
start_retry
Starting a child again when the operating system refuses it for lack of processes, within a bounded wait. What: Starting a child process again when the operating system refuses it for lack of processes. Why: A host that has run out of processes (a process limit, a cgroup limit) refuses a start with EAGAIN, which Rust reports as ErrorKind::WouldBlock. A refused start started nothing, so starting again is always safe, and such a shortage usually ends within moments. Without this the first refusal ends a commit that the next attempt would have finished. The wait is bounded, so the wrapper still never stalls: a shortage that outlasts the budget fails with the same error as before plus how often the start was refused.
startup_gate
The startup step of a command: isolated merges whose process died, and what an earlier release left behind. What: The startup step of a wrapped, read-only or direct command: settle an isolated merge whose process died, stop on a commit journal of the previous executable’s format, and mention commit records an earlier release left behind. Why: A commit is landed by the resident service, which recovers its own journal, so no commit transaction exists to recover. What stays is state other code still writes (the intent record of an isolated merge) and state an earlier release wrote (the single-journal directory and the registry of commit transactions): the first is settled before anything reads the checkout, the others are named to the person, because nothing here reads them and an interrupted commit of that release is for the person to look at. An empty registry costs one directory read and no Git process.
subvolume_anchor
An open directory that every operation inside it is addressed through, so a replaced path cannot redirect them. What: An open directory used as the fixed point of every operation inside it: the directory is opened one component at a time without following symbolic links, and names inside it are then addressed through its descriptor. Why: The tree passes write into a checkout that other programs may be changing (the catch-up and the carry-back run against a tree in use). A path such as destination/d/file is resolved again for every call, so a directory d replaced by a symbolic link between two calls would send the write, the removal or the permission change outside the tree. Opening each directory without following links and addressing its children through /proc/self/fd/<descriptor>/<name> makes every call land in the directory that was checked, whatever happens to its path afterwards. Only the last component of a child path is still looked up by name, and the operations on it (exclusive create, rename, unlink, lstat) never follow a link there.
subvolume_arguments
The grammar of git cli-git migrate-to-subvolume. What: The grammar of git cli-git migrate-to-subvolume, the wrapper’s command that converts the main checkout into a btrfs subvolume, and the help text. Why: The command name says what it does and follows the other multi-word git cli-git spellings (service status). Running it migrates after the preflight; --dry-run previews; --rollback and --remove-old are the two ways out; the rest only switch off a part of the migration. Modes that exclude each other, and switches that belong to one mode, are refused with the reason.
subvolume_command
git cli-git migrate-to-subvolume: root discovery, modes and exit statuses. What: git cli-git migrate-to-subvolume: find the repository’s main checkout from the working directory, run the requested mode (migrate, rollback, remove the old tree) and turn the result into an exit status and text. Why: The command runs for minutes (the real checkout took about sixteen minutes of passes), so progress lines go to standard error as they happen; refusals and failures end with the cause and every way forward. Exit status 0 means done, already done or previewed, 1 means a condition refused it before anything changed, and 2 means a step failed after the journal began, with the instruction to run the command again or to roll back. The main checkout is found by reading .git in process (a directory, or a gitfile naming the common directory), never by asking Git.
subvolume_error
The failure type of the subvolume migration and its simulated-kill hook. What: The failure type of the subvolume migration and the test hook that simulates a kill. Why: Every step names what it did to which path and why it stopped, so the report can tell the person the cause, the state the journal holds, and the way forward. The interrupt hook stops a run at a named point exactly as a kill would (all state is on disk or in the journal; nothing else survives a kill), so the resume controls can stop at every journal step without killing the test process. Release code passes Interrupt::never().
subvolume_index
The stat rewrite of a Git index file’s bytes. What: The stat rewrite of a Git index: the bytes of an index file in, the bytes of the same index out, with the stat fields of each entry replaced by what the file on disk reports now. Why: A reflink copy gives every file a new inode number and change time, so the first git status in the copied tree finds every entry stat-dirty and hashes every file (22.1 s measured against 0.17 s with the rewrite, and the clean filter of Git LFS runs on LFS files). The rewrite touches an entry only when the file’s size and modification time equal the entry’s, so a file that really changed stays dirty. Entry lengths never change, so the entries are edited in place. The accelerator extensions that record stat-derived state (UNTR untracked cache, FSMN fsmonitor token) or offsets into the entries (EOIE, IEOT) are dropped, because Git rebuilds them; a split index (link) or a sparse index (sdir) is left untouched. The checksum is recomputed with the hash that verifies the old one (SHA-1, else SHA-256); an index written with index.skipHash has an all-zero checksum and keeps one. Versions 2, 3 and 4 are read (Documentation/gitformat-index.adoc).
subvolume_index_files
The linked worktrees of a main checkout and the index stat rewrite applied to files. What: The linked worktrees of a main checkout, found from .git/worktrees/*/gitdir, and the stat rewrite applied to the index files of the checkout and of those worktrees on disk. Why: The subvolume migration copies the main checkout and the linked worktrees inside it, so each of their indexes needs the rewrite of subvolume_index.rs. A file in .git/worktrees/<name>/gitdir holds the path of the worktree’s .git file, so the worktree is its parent directory (the same reading the migration script used). An index is replaced through index.lock as Git replaces it, so a running Git command is never raced: the lock is taken before the index is read, and when it exists the index is left as copied and the report says so. The migration holds the locks itself while it completes the new checkout, and its rewrite then runs under them.
subvolume_journal
The migration journal beside the checkout. What: The journal of a subvolume migration: a small JSON file beside the checkout that records which steps finished, written atomically after each step. Why: A rerun after a kill at any instant resumes from it: a step marked done is skipped, a step marked started is repeated (every step is idempotent), and a step with no entry has not begun. The file lives in the checkout’s parent directory (.cli-git-migrate-<name>.journal.json), outside the tree that is copied and exchanged, so it survives the exchange. It is replaced by writing a sibling file and renaming it, so a kill never leaves half a journal. serde_json is already a dependency of the crate.
subvolume_lock
The index locks the migration holds, marked with a token that names the migration. What: The index locks the migration holds: one index.lock file for the main index and one for the index of each linked worktree, each carrying a token that names this migration. Why: Git writes an index by creating index.lock exclusively, writing it and renaming it over index. A migration that swaps the checkout directory under such a writer, or copies a half-written lock into the new tree, damages the index. So the migration creates the lock files itself, in the old tree before its last copy and in the new tree before the exchange, and keeps them until the indexes are rewritten. The token makes a lock the migration’s own: a lock file that exists when the migration tries to create one is accepted only if it holds this migration’s token, so another Git command’s lock is never mistaken for it, never exempted from the refusals, and never removed. The linked worktrees’ administrative directories live inside .git, so their locks and indexes travel with the main checkout whether or not the worktree directory is inside it.
subvolume_migrate
Converting a main checkout into a btrfs subvolume, resumably. What: Convert a main checkout into a btrfs subvolume at the same path, in process, resumably. Why: The steps are those of the proven script (doc/handover/cli-git-subvolume-migration-script.md), without any program started: create <root>.subvol-new as a subvolume; create the linked worktrees inside it as nested subvolumes; copy the tree with cloned data; repeat the copy as a delta; hold .git/index.lock, run the last delta and swap the two directories in one atomic step; rename the old tree aside; carry over files newer in the old tree; rewrite the Git index stat fields; verify. Each step is recorded in the journal (subvolume_journal.rs), so a rerun after a kill at any point resumes: a finished step is skipped and an unfinished one is repeated, and all of them are idempotent. The old tree is never deleted here.
subvolume_platform
The operating-system operations of the migration behind one trait, and the Linux implementation. What: The operating-system operations the subvolume migration needs, behind one trait, and the Linux implementation that calls the primitives (fs_kind, fs_btrfs_ioctl, fs_clone_exchange, fs_mount_options). Why: The migration’s journal, resume, rollback and refusal logic is the same on any filesystem; only five operations need btrfs. With the operations behind a trait, the controls run the whole logic on a plain directory tree with a stand-in platform where there is no btrfs (the container gate), and run it against the real platform on btrfs (the host task). doc/planning/cli-git-btrfs-worktree.md is the design the primitives come from.
subvolume_preflight
The paths of a migration and the refusals that stop it before anything is created. What: The paths of one migration and the refusals that stop it before anything is created: every refusal names its cause and every remedy that applies. Why: Converting a checkout swaps its directory under every process and worktree, so the migration starts only when the filesystem, the checkout and the state left by an earlier run allow it. The conditions are those of the proven script (doc/handover/cli-git-subvolume-migration-script.md, “Refusals”) with the rest of the owner’s rules: nothing is spawned, and a refusal is text a person can act on. A condition checks only metadata (the dry run reads .git and .git/worktrees/*/gitdir and never walks the tree).
subvolume_remove
Removal of a directory tree whose directories may lack owner write permission. What: Remove a directory tree, making each directory the user owns writable on the way down. Why: The migration preserves permission bits, so a checkout can hold a mode-555 directory with files in it. Unlinking a file needs write permission on its directory, so std::fs::remove_dir_all stops at the first such directory and both the deletion of the old tree and the emptying of a subvolume fail partway. Every directory is opened without following links and given the owner’s read, write and search bits through its descriptor before the tree is removed; a directory the user does not own keeps its mode and the removal reports the operating system’s refusal.
subvolume_rollback
Undoing a migration and deleting the old tree. What: Undo a migration (--rollback) and delete the old tree after a finished one (--remove-old), plus the removal of a subvolume tree that both depend on. Why: Rollback carries back what was written to the new root since the exchange (files newer there win), swaps the trees again, rewrites the restored indexes’ stat fields (the carried-back index describes the new tree’s inodes), and deletes the new subvolume deepest nested subvolume first, because the kernel refuses to destroy a subvolume that holds another (ENOTEMPTY). Where the mount does not allow an unprivileged destroy, an emptied subvolume is removed with rmdir, which the kernel allows for an empty subvolume root. The old tree is an ordinary directory, so deleting it is a file walk, never a subvolume destroy.
subvolume_run
The state a migration command carries between its steps. What: The state one migration command carries between its steps: the platform, the paths, the journal, the progress sink, the list of files this user may not read, and the helper that runs one tree pass and reports it. Why: The migrate, rollback and remove-old commands share it, so each pass is timed, counted and reported the same way and the unreadable-file list is written the same way after every pass (a kill between passes loses nothing).
subvolume_sha
SHA-1 and SHA-256, for the checksum of a rewritten Git index. What: SHA-1 and SHA-256 of a byte string, written out in full. Why: The last bytes of a Git index file are a checksum of everything before them (SHA-1, or SHA-256 in a repository of that object format), and Git refuses an index whose checksum is wrong. The index stat rewrite of the subvolume migration must write a new checksum. The crate has no hash function, a new dependency was not shown to be needed for two short functions, and the controls compare both functions with the published test vectors and, in the migration controls, with the checksum real Git accepts.
subvolume_sync
One idempotent pass that makes a directory tree match another, in process. What: Make one directory tree match another, in process: copy what is missing or changed (cloning file data where the filesystem can), optionally delete what the source no longer has, or only add what is newer in the source. Why: This is the work cp --archive --reflink=always, rsync --archive --delete and rsync --archive --update did in the migration script, without starting those programs. One idempotent function serves all three passes, so a pass interrupted at any point is repeated safely: an entry already equal (same type, size, modification time and permission bits) is left alone. The walk uses a work stack, not recursion, so a deep tree cannot overflow the stack; a directory’s own attributes are applied after its children, because adding a child changes its modification time. Files this user may not read (for example root-owned mode 600 logs) are left where they are and listed; any other failure, and a directory this user cannot list, stops the run (the rule of the 2026-11-01 real run: copying around an unreadable directory with deletion on would erase its subtree from the copy). Every entry is written under a temporary name in its directory and renamed over its name when complete, so a kill never leaves a half-written file at a name a later pass would take for a newer one. The destination is reached through open directory descriptors (subvolume_anchor.rs), so a directory replaced by a link during the pass redirects nothing. An update-only pass never removes a destination entry that may hold newer work: a directory, or an entry of another type that is not older than the source, is kept and listed.
subvolume_sync_attrs
Copying owner, permissions, times and extended attributes onto a copy. What: Copying the attributes of one directory entry onto its copy: owner, permission bits, access and modification times to the nanosecond, and extended attributes (user attributes and POSIX ACLs included); and recreating a named pipe. Why: cp --archive and rsync --archive --xattrs did this in the migration script. The index stat rewrite trusts that sizes and modification times survive the copy, and Git compares the mode and owner of each entry, so the copy must carry them. Failures that an unprivileged user must expect are tolerated and counted, never silent: an owner other than the caller’s cannot be set (EPERM), and a security label (security.selinux) is set by the policy for a new file, not by the caller. A failure that no unprivileged user should meet (a permission change or a time change refused for lack of space, say) is an error, because the copy would be recorded as faithful when it is not. The attributes are applied through the descriptor of the copy, so a path swapped for a link while the pass runs cannot redirect them; only a symbolic link, which cannot be opened, is addressed by its path.
systemd_dbus
A minimal D-Bus client for the user session bus, for the provisioning’s two calls. What: A minimal D-Bus client for the user session bus: authenticate as the current user, say Hello, and call methods of org.freedesktop.systemd1.Manager with string arguments, each bounded by one deadline for the whole exchange. Why: The wrapper provisions socket activation itself, and may start no program to do it (systemctl would be one). The supported surface of the user manager is its D-Bus API (org.freedesktop.systemd1(5)), reached through the session bus; the manager’s own private socket is documented as private to systemd. Only Reload, ResetFailedUnit, StartUnit, StopUnit and the state query (GetUnit, then a property read) are needed, all with at most two string arguments, so the writer and the reader below cover exactly the types those calls and the bus’s replies use (s, o, g, u, and a variant holding s) and stop at anything else. The reader is total over arbitrary bytes (a fuzz target holds it to that). The comparison with a D-Bus crate is in doc/planning/cli-git-resident-service.md, “Provisioning”.
traditional_hook_warning
The non-blocking warning for a configured core.hooksPath, decided from the configuration snapshot alone. What: The non-blocking warning a wrapped commit prints when the configuration sets core.hooksPath. Why: Traditional hooks (executable scripts in .git/hooks or in a core.hooksPath directory) are unsupported, and their behavior with the wrapper is undefined. The commit path may not look for them: a directory scan would cost file system calls on every commit. It already holds one free signal, the configuration snapshot it read for other purposes, so a configured core.hooksPath is the only trigger, decided from memory alone. Finding the scripts themselves is the job of git cli-git doctor. The line is a configuration-warning event, which never blocks a command.
transaction_git
Running real Git for transaction, recovery and landing work. What: Running real Git for transaction, recovery and landing work: captured output, an optional private index and object store, extra or removed variables, and bytes on standard input. Why: Every transaction step asks Git a question or writes an object; one runner keeps every such start an argument array (never a shell string) carrying the wrapper’s child environment overlay (lock PID injection), as the incumbent’s runTransactionGit does.
transaction_journal
The schema-version-2 journal records of one commit transaction and their filenames. What: The values a commit’s preparation and the service’s landing share about the repository state they work on: the base, the symbolic head, the private index mode, the sequencer conclusion, the ref storage format and a policy-added path. Why: The records the earlier transaction wrote into its registry (preparing, prepared, ref-updated, landing and index-lock records) are gone with the transaction; only the types the service route still passes between its steps remain (src/policy-engine/commit-transaction-journal-states.ts is their incumbent).
transaction_owner
The owner record of one commit-transaction directory and its liveness rule. What: The owner record of one commit-transaction directory (owner.json, schema version 2) and the liveness rule that decides whether recovery may touch the transaction. Why: The owner record is written before the directory is published, so recovery can skip a live owner’s transaction at every phase. Both wrappers write and judge the same record (src/policy-engine/commit-transaction-owner.ts).
transaction_phase
Test-only transaction phase markers, compiled into the release executable and armed only by an environment variable. What: Test-only phase markers of the isolated merge and of a commit headed for the service, for crash and interleaving suites. An explicit environment variable arms one marker: CLI_GIT_TEST_ONLY_PHASE_SIGNAL=<phase>:kill ends the process with SIGKILL at the phase, <phase>:kill:<directory> writes <directory>/<phase>.reached first, and <phase>:pause:<directory> writes that marker and waits until <directory>/<phase>.release exists. A repeatable phase numbers its files by occurrence: <phase>-<n>.reached. Why: Crash suites need a deterministic point to kill the wrapper. The marker is compiled into the release executable, but only the explicit variable arms it, and a malformed value fails the invocation at every phase it reaches instead of being ignored (src/policy-engine/commit-transaction-test-phase.ts).
unported
The work of the installed cli-git that is not ported, and the refusal notice. What: The work of the installed cli-git that this executable does not do yet, as one typed list, and the notice printed when a command needs any of it. Why: A command that needs unported work must stop with exit status 2 and say what is missing. Forwarding it instead would run the command without the protection the installed wrapper gives it, and nobody would see that it happened.
wait_limit
How long a command waits for the commit service’s hold on the index, and the variable that sets it. What: How long a command waits for the commit service’s own hold on the index, and the variable that sets it. Why: A hold that is alive but stuck would otherwise stall every later command with no output at all. The wait ends at a generous backstop (CLI_GIT_LOCK_WAIT_SECONDS, thirty minutes by default), read by the wrapper that waits and by the service that waits for a foreign index.lock before it lands a commit; it belongs in the environment of the unit that runs the service.
wait_progress
The shared clock of every wait notice: the grace, the repeat interval and a queued commit’s place in line. What: The shared clock of every wait notice: when the first line of a wait is due, how often it repeats, and the place in line a queued commit holds. Why: A commit that queues behind other commits, a lock, a reservation or a child process must say so, and say it again at a steady interval, or the caller cannot tell a queued commit from a hung one (issue 1811). Each wait loop feeds its elapsed time to a Ticker and prints a line when the ticker says one is due, so the decision is a function of elapsed time alone and a test can drive it with chosen durations. The interval comes from CLI_GIT_WAIT_NOTICE_SECONDS at the production entry of each wait.
work_item
The durable work-item record for asynchronous work (review, push, maintenance) left for a resident consumer. What: The durable work-item record: a small file in the Git directory that says “this landed object needs this asynchronous work”, written by the wrapper and drained by a separate resident consumer. Why: A wrapper process exits when its command ends, and it may start no program, so work that must not delay a landing (the standards review, later auto-push and maintenance) is left as a record instead. The record is generic: a kind, the landed object and an id derived from both, so a retry cannot duplicate it. The design, its failure cases and the consumer contract are in doc/planning/cli-git-standards-review-port.md. Nothing calls this yet.
work_item_recovery
What: The recovery half of the work-item record: the claims whose lease ran out go back to pending (or to failed), and a killed recovery’s leftovers are removed. Why: A consumer killed at any instruction is noticed by its lease, never by a process id; the code is apart from work_item.rs so each file stays within the size limit, and work_item.rs re-exports what callers use.
worktree_completion
Bringing added paths’ worktree copies to their landed bytes. What: Bringing each added path’s worktree copy to the landed bytes after a commit landed, with descriptor-bound checks that never overwrite a concurrent edit. Why: An added path’s committed bytes differ from its worktree copy (a policy corrected it); the copy is replaced only while it still holds the original bytes, through a same-directory temporary file. A copy holding neither is kept and reported, since the commit already landed (src/policy-engine/commit-transaction-added-paths.ts, -worktree-check.ts, -worktree-replace.ts).
worktree_copy
The linked-worktree ignored-state copy around one forwarded Git invocation: the integrator’s entry point. What: The linked-worktree ignored-state copy around one forwarded Git invocation: the public entry point the integrator calls, the settlement and summary it writes, and the exit status it returns. Why: A worktree that Git creates (or a bare repository’s) needs its ignored files from the source worktree, copied under one settlement lock per repository and recovered first when an earlier copy was interrupted.
worktree_copy_compare
Exact comparison of manifest entries against the filesystem, and the source stability check after staging. What: Exact comparison of one manifest entry against the filesystem: its kind, its retained mode, its symbolic-link target text or its regular-file bytes. Also the final check that the source still matches the stage after staging. Why: A destination must receive exactly what was staged from the source, and a source that changed during the transfer must block the destination.
worktree_copy_error
The one failure type of the linked-worktree ignored-state copy and its diagnostics. What: The one failure type of the linked-worktree ignored-state copy, with the complete user-facing diagnostic in its message. Why: Every copy step fails with a message that names the path it could not handle and, when there is one, the operating system’s cause. The incumbent renders the cause after the message with a fixed separator, so both wrappers print the same words.
worktree_copy_forward
Which forwarded invocations the copy applies to, and the child run that carries its settlement lease. What: Running a forwarded Git command through the linked-worktree ignored-state copy: which invocations the copy applies to, and the child-process run that carries the settlement lease. Why: A command that creates or moves worktrees must stay under the settlement lock while real Git runs and the ignored state is copied, and a command run from a linked worktree must first recover an interrupted copy; replacing the process with Git would drop both duties, so these invocations run Git as a child. The main checkout is a source only for the configured rules file, so it takes this path only when the command creates a worktree and the configuration names that file.
worktree_copy_install
Installation of a validated stage into a new worktree, with preflight, batches and rollback on failure. What: Installation of one validated stage into a newly registered worktree: preflight of every existing destination entry, batched creation with its durable records, mode application and rollback of this installation’s own paths when it fails. Why: A destination that already holds an identical entry is accepted, and a differing entry is never overwritten, so the copy cannot destroy user state. Recording each batch before and after it runs bounds the work an interrupted run leaves unrecorded.
worktree_copy_install_entry
The exclusive creation of one destination entry from the stage, and the identity captured after it. What: The exclusive creation of one destination entry from the private stage, the identity captured right after it exists, and the creation of the unselected parent directories an entry needs. Why: A regular file is installed as a hard link to its staged copy, so it appears with its final bytes and mode in one step; the identity captured after creation is the only proof later rollback accepts.
worktree_copy_install_log
The append-only install log of one stage: per-batch intents and creations, with resumption. What: The append-only install log inside one transaction’s private stage: one synced JSON line per batch of intents and one per batch of proven creations. Why: The journal header changes only with the phase, so the per-batch records live here; bytes written then grow with the entry count instead of with the square of it.
worktree_copy_journal
The durable journal of one copy: exact encoding, strict validation, private storage and removal. What: The durable journal of one destination’s linked-worktree copy: its exact JSON encoding, strict validation of every field recovery will act on, private storage under <common>/cli-git-worktree-copy/v1, and removal of a finished transaction’s stage and journal. Why: A crash can happen after any step, so the journal must say which phase the copy reached and which paths it may have touched, and recovery must never act on a journal it cannot prove is its own.
worktree_copy_lock
The settlement lock that serializes copy installation and recovery, its owner record, and inherited leases. What: The settlement lock that serializes worktree-copy installation and recovery per common Git directory, its owner record, liveness-based retirement of dead owners, and the inherited-lease check nested Git runs use. Why: Two copies into one repository must not interleave, but a commit in a linked worktree must never wait for them; only an owner proven alive may be waited for, and only an owner proven dead may be replaced.
worktree_copy_manifest
The deterministic parent-first manifest of a copy, and the mode application to a staged copy. What: The deterministic, parent-first list of entries a copy transfers, read without following symbolic links, and the application of the retained permission bits to a staged copy. Why: The stage and the source are compared entry by entry, so both sides must enumerate in the same order and classify the same way. Sockets, FIFOs and device nodes stop the walk before any of them is opened.
worktree_copy_recovery
Recovery of interrupted copies: registration checks, discards with notices, resumption, and the pending check. What: Recovery of interrupted linked-worktree copies: every pending journal is resumed, completed, or discarded with a notice; malformed state fails closed and stays; the pending check that forwarded commands run first. Why: A copy interrupted by a crash must not leave a worktree half-filled forever, but recovery may only act on journals whose destination is still a registration of this repository and whose stage is still private.
worktree_copy_registry
Which linked worktrees a worktree-creating Git command registered, before and after it ran. What: The linked worktrees a worktree-creating Git command registered: the administrative identities before real Git ran, the identities after it returned, and the canonical root of each new one. Why: A worktree that Git registered before failing still receives the ignored files, so the comparison runs after Git returns whatever its status was. The main worktree is observed only when the caller names repository paths to copy from it (observe_copy_source), so its other invocations cost no filesystem reads.
worktree_copy_rollback
Removal of the paths a failed installation created, only where their identity and content still prove them cli-git’s own. What: Removal of the paths one failed installation created, child first, and only where the filesystem still proves they are cli-git’s own. Why: A failed installation must not leave a partial worktree, but it also must never delete a path the user or another process put there after cli-git created it.
worktree_copy_selection
The ignored source paths a linked-worktree copy selects, and the path validation every selection passes. What: Which source paths the linked-worktree copy selects: the ignored roots Git reports under the standard exclusion stack, the ignored special entries (sockets, FIFOs, devices) Git’s listing omits, and the validation every repository path passes before it touches the filesystem. Why: Git decides what is ignored; the copy only adds the entries Git’s index-oriented listing cannot report, and it never trusts a path that could leave the worktree.
worktree_copy_stage
The private, validated stage of one source’s ignored state beside a destination worktree. What: The private, validated copy of one source’s ignored state, staged in a mode-0700 directory beside a destination worktree. Why: The stage sits on the destination’s filesystem, so a file clone stays local and the install is a rename or a link. The source is copied entry by entry and compared again at the end, so a source that changed during the copy never reaches the destination.
worktree_copy_stage_claim
The durable claim on a stage container’s name, and the removal of stages an interrupted copy left behind. What: The durable claim on a stage container’s name, written before the container exists, and the cleanup of containers whose copy was interrupted before its journal was written. Why: Staging a worktree’s ignored state takes minutes and gigabytes, and the journal is written only after it. A process killed in between (a signal, the out-of-memory killer, a power loss) leaves a stage nothing names. The claim names it first, so the next recovery pass, which runs only when no live copy holds the settlement lock, removes it with a notice instead of leaving it to fill the disk.
worktree_copy_transaction
One journaled installation to an end, resumption from the log, and synchronization of a created worktree. What: One destination’s journaled installation to an end, and the synchronization of every created worktree. The journal records the phase, the install log records each batch’s claims and creations, and a finished or failed installation always removes its journal and stage. Why: A failed installation must not stay pending, because recovery would replay the same failure before every later command; an interrupted one must resume from exactly what it recorded.
worktree_identity
Repository and worktree identity as reported by real Git. What: Ask real Git which repository and worktree an invocation selects. Why: Configuration lives at the worktree top level, and worktree policies need to know main versus linked. Git applies -C, --git-dir, --work-tree and its environment itself, so the wrapper never re-implements that selection.
worktree_removal_rescue
What becomes of the unshown push notices and unfinished pushes of a linked worktree that Git removes. What: What happens to the unshown push-failure notices and the unfinished backup pushes of a linked worktree when Git removes that worktree: before git worktree remove or git worktree prune runs, the wrapper copies those records into a snapshot under the common Git directory, and a later wrapper command settles each snapshot, turning it into notices that any worktree shows when the worktree is gone and discarding it when the removal did not happen. Why: A worktree keeps its records in its own Git directory, which Git deletes with the worktree. Without this a failed push nobody had seen yet, or a push the service had not run yet, would vanish with no word, and the person would never learn that a branch is not on the remote. The wrapper hands the process over to Git for the removal, so it cannot look afterwards; the snapshot is taken for every linked worktree that has records and settled later. A Git directory that is gone proves the removal. A Git directory that is still there proves a refusal only once the command that took the snapshot has ended (the process keeps its identity when it becomes Git, so the snapshot names it) and the worktree’s checkout is intact; while that command lives, or a checkout is missing from a Git directory that is still registered, the removal may be under way or cut short, and the snapshot is kept. A snapshot that cannot be fully promoted, or fully taken, is kept as far as it goes and reported on standard error, because a line in the diagnostic log is not a word to the person.
wrapped_command
The lifecycle of one wrapped Git command, from refusals to forwarding. What: Decide what one wrapped Git command does once real Git is known: forward it, possibly rewritten, or stop with policy events or a refusal. Why: This is the whole lifecycle of a forwarded command in one place and in the installed wrapper’s order: unported work is refused, configuration is loaded only for commands that need it, the policy pass runs, and a real push is stopped until its own lifecycle exists. A read-only command never reads configuration and asks Git at most where it runs.
wrapper_controls
Wrapper-only control spellings, their meanings, and removal before the subcommand. What: The wrapper-only control tokens, what each one means, and their removal from the arguments that come before the Git subcommand. Why: Git refuses options it does not know, so every control must be gone before Git runs, and every policy decision must read the arguments without them. A control is recognized only where Git itself would look for an option, and it is removed by its position, never by its spelling, so an equal-looking value or path is kept.
wrapper_invocation
Removal of every wrapper control from one invocation, by position. What: Remove every wrapper control from one invocation, before and after the subcommand, and say how the command’s own arguments were read. Why: Detecting a control and removing it must be one act. This module is the only place that removes wrapper tokens: positions come from Git’s own option table of the command where one is ported, and are deleted by position, so a control never reaches Git and a message, value or path that spells a control is never removed.

Macros§

service_error
What: Writes one formatted line at the err priority, with its time. Why: Something the service needed failed; journalctl --priority=err shows it.
service_log
What: Writes one formatted line to the service’s log, with its time. Why: Every line the service writes states its own time (stamped_line); the macro is the one way to write a line with arguments.
service_warn
What: Writes one formatted line at the warning priority, with its time. Why: Something did not go as intended and the service went on; journalctl --priority=warning shows it.