The admin side runs its own git, and the site's git refuses to run on an admin thread. Only lichen's own code runs on the admin side, and the admin side never runs git in the site's .git.
HubGit (in lichen-git-command) is the admin side's one git. It runs in a repository of its own (at: the hub, a fetch's staging), with a work tree of its own (work_tree: a Pages deploy's), or in no repository (no_repository: a config's bytes, git --version). It has no system or global config, no prompt and no hooks, runs in the server's base environment, and refuses a path inside the site's folder, which the admin thread names as it reads its launch.
The admin side's calls move onto it: the hub's one git (folder::git), this server's git in the hub and a fetch's staging, the push and fetch to the remote, a Pages deploy's checkout, and the remote's config read and write. The symlink screen of what a fetch brought is given its caller's git (HubGit::at), where it ran site git on the admin thread. Startup's git check (require_program) is HubGit::no_repository(), where it ran site git in the site folder.
The marker. The admin thread and its runtime's threads carry a thread-local, set where they are marked as the admin runtime's. GitCommand::site, site git's one door (SiteGit builds on it), answers an error on a marked thread, in every build. HubGit refuses an admin thread's git when no site folder is named.
The scan. In the admin side's files, a structure test refuses GitCommand, SiteGit, the site's git environment and a raw thread, except for four named exceptions. Files both sides use are not in the scan; there only the marker refuses site git at run time.
ADMIN-HUB-GIT, the whole phase in one reading PR. Plan: https://tangled.org/notplants-bot.bsky.social/lichen-orchestrator/blob/main/machine-docs/plans/ADMIN-HUB-GIT-2026-11-05.md
Only the phase's Rust product code is here: every .rs file under crates/<crate>/src/ (no tests folder in its path), which is what e2e/ci/count_product_code_lines.py counts as product. The head is the base plus one commit that sets each of those files to its content at the end tip (added, changed, deleted and renamed files alike). Tests, docs, scripts and non-Rust files (including Cargo.toml and Cargo.lock) are not in it, so the crates here are not meant to build on their own. Reading copy; nothing here is to be merged.
Source PRs on lichen.page.review (product), in order #
- #3795
HubGit, the admin side on it, the symlink screen's git (ADMIN-HUB-GIT unit 1) - #3798 the marker, site git refusing it,
require_program(unit 2) - #3800 the scan widened, with the files both sides use named (unit 3)
Base, end tip, count #
Base 10e4eae604ce6825c4481652be40de641805d84a. End tip 19499e6e7b49e9bf321ce302e192f91a02d4bbc1. Head 7bf95ed1cd34f8eb8d18a79a054bb1ed080f6eeb (23 files: 23 added or changed, 0 deleted).
Counter, base to end tip (the phase as lichen.page.review has it):
count-product-code-lines 10e4eae604ce..19499e6e7b49: NET +28 · counted +203 -175 · moved out of the counted set +0 · comment-only deletions flagged 3 · macros flagged 0 · tests-folder code flagged 0
Counter, base to head (this PR):
count-product-code-lines 10e4eae604ce..7bf95ed1cd34: NET +28 · counted +203 -175 · moved out of the counted set +0 · comment-only deletions flagged 3 · macros flagged 0 · tests-folder code flagged 0
The two are equal.
Check #
git diff base head lists only counter-product Rust files, and the head's content equals the end tip's for each of them.
Record #
ADMIN-HUB-GIT is COMPLETE at stack 19499e6e7b49e9bf321ce302e192f91a02d4bbc1 (coordination report machine-docs/reports/ADMIN-HUB-GIT-2026-11-11.md). Every product commit in this range is the phase's own.