Many writers commit to the same brain checkout on one machine: interactive sessions, sub-agents, cron jobs, the post-commit hook, gbrain's own page writes and sometimes a host sync daemon. Every one of them commits and pushes with one command, and none of them can stash, rebase, revert or ship another writer's half-written files.
Say to your agent: "Commit the meeting notes you just wrote to my brain and push them."
Your agent runs scripts/brain-commit-push.sh "meetings: 2026-04-03 product review" meetings/2026-04-03.md
(or gbrain git commit-push -m "<msg>" -- <paths> when the brain has no helper).
Say to your agent: "Bring my brain checkout up to date before you start writing."
Your agent runs scripts/brain-commit-push.sh --pull (or gbrain git commit-push --pull).
Say to your agent: "The push conflicted on people/alice-example.md. Keep the version on GitHub."
Your agent runs gbrain git commit-push --push-only --expect-remote <sha> --resolve people/alice-example.md=theirs,
copying the remote commit from the conflict report.
Skills used to tell each agent to run git pull --rebase (sometimes with
--autostash) and then a plain push, directly in the shared working tree. A
rebase stashes every other process's uncommitted work for as long as it runs.
When it crashes, times out, hits a busy index.lock or conflicts, it leaves a
.git/rebase-merge directory with someone else's files inside its autostash.
Every later pull then refuses, and an agent that "repairs" it by hand can
silently revert another agent's in-flight edit. A repo-wide git add -A or
git commit -a has the same problem from the other side: it commits whatever
another writer has half-written.
gbrain git commit-push does five things, in this order:
- Takes this checkout's commit lock, briefly. One commit lock per
checkout, held only while it stages and commits (and later while it
fast-forwards the checkout), never across the network. A second writer
waits (60 seconds by default) and then fails with
commit_lock_busyinstead of skipping silently. The push itself runs under a separate push lock, so a slow remote never blocks commits or gbrain's own page writes. - Commits only the paths you name. It stages and commits exactly those
paths (
git add -A -- <paths>,git commit --only -- <paths>). Another writer's dirty files are never staged, committed, stashed or touched. A file another writer already staged under a directory you named is refused (commit_prestaged_paths) instead of swept in; name the file to claim it. A path outside the brain's source root is refused (commit_outside_source_root). When git refuses the commit (a hook, a missing identity, a busy index), the paths are unstaged again, so nothing is left in the shared index for another writer's commit to pick up. - Pushes your commit, and merges remote commits in memory when the push is
rejected. It fetches, builds the merge without touching any working tree
(
git merge-tree --write-treeon git 2.38 and later, a three-way merge in a temporary index on older git) and pushes the merge commit. Local commits whose patch the remote already has (a cherry-picked or rebased copy) are left out of that merge: the local commits are replayed in memory onto the remote tip without them, so a copy the remote edited since never conflicts and a copy the remote reverted is never put back. The merge commit's message lists the commits it left out. It makes up to eight attempts, backing off, while the remote keeps moving. One push at a time runs per checkout and it publishes the newest local commit, so writers that queue behind it share its push. The post-commit hook never waits: when a push is already running it asks that push to publish its commit too (push_deferred). - Advances the shared checkout fast-forward only.
git merge --ff-onlyrefuses, and changes nothing, when another writer moved HEAD or has a local edit in a file the merge must change. The push has still landed; the checkout just lags until the next commit-push or pull (pushed_checkout_lagging). - Stops on a real conflict. When both sides changed the same file, it changes nothing and asks you which version wins (conflicts).
Remote commits therefore arrive as merge commits named
gbrain: merge origin/<branch> into <branch>, so brain history is no longer
linear. That is the price of never rewriting a working tree other writers
are using.
A brain hardened with gbrain sources harden has a committed helper,
scripts/brain-commit-push.sh. It hands the same arguments to
gbrain git commit-push, so either form works:
# Commit and push the files you wrote (paths are relative to the current directory)
scripts/brain-commit-push.sh "people: add alice-example" people/alice-example.md
gbrain git commit-push -m "people: add alice-example" -- people/alice-example.md
# Publish commits already on the branch (the hook's job; also the retry after a push failure)
scripts/brain-commit-push.sh --push-only
gbrain git commit-push --push-only
# Bring remote commits in (fast-forward, or an in-memory merge); never pushes
scripts/brain-commit-push.sh --pull
gbrain git commit-push --pull
# Commit and push the write-through files gbrain could not commit (doctor's dirty_age names them)
gbrain git commit-push --pending
# Preview: prints the paths, branch and lock it would use; takes no lock, writes nothing
gbrain git commit-push --dry-run -m "people: add alice-example" -- people/alice-example.mdFlags: --path <dir> acts on a checkout other than the one containing the
current directory (the paths are then relative to <dir>, like git -C),
-m repeats like git commit (each one is a new paragraph, so a second -m
carries a trailer), --json prints one JSON result (or the error envelope),
--require-clean makes --pull skip when the checkout has any local edit
(the rule the unattended pull cron uses). The command is local only: it pushes
with this machine's git credentials and is never exposed over MCP.
When gbrain is not installed on the machine, the helper falls back to a locked
path-scoped commit and a plain push. A rejected push then fails loudly (exit 4)
and tells you to install gbrain and run gbrain git commit-push --push-only;
the fallback never integrates remote commits itself.
In a managed canonical worktree (gbrain sources writer git-durability) its
persistence owner is the only Git publisher. Save pages with put_page,
edit_page, remember or capture. For files written there outside gbrain
(see files that are not pages),
gbrain git commit-push -m "<msg>" -- <paths> commits only the named paths
under the checkout's commit lock and reports committed_local: it never
fetches, merges, pushes or moves the checkout, and refuses --pending,
--branch, --resolve and --allow-outside-source-root there
(commit_paths_invalid). The commit reaches the remote on the owner's next Git
publish. --push-only without --keep-checkout is still refused with
writer_coordinator_required.
A checkout with no Git remote at all (git remote prints nothing and the
branch names none) has nothing to publish to: gbrain git commit-push -m "<msg>" -- <paths>
commits only the named paths under the commit lock and reports
committed_local with no_remote: true, saying nothing was pushed.
--push-only, --pending, --branch, --resolve/--expect-remote and
--require-clean are refused there (commit_paths_invalid). Add a remote and
set the branch upstream, then gbrain git commit-push --push-only publishes the
local commits. A branch that names a remote which does not exist still refuses
(git_unavailable).
The one form a managed worktree accepts is --push-only --keep-checkout. It
publishes the commits already on the branch, merging a moved remote in memory,
but never moves the checkout and skips its pre-push hook, so
gbrain sources refresh stays the only way remote files enter the checkout.
The owner's Git effects use it on their own when another machine pushed first;
you run it only to settle a conflict the owner reports.
Say to your agent: "My brain's Git backups stopped pushing. Find out why and get them moving again."
The agent reads gbrain sync status --source <id> --json (git_push_holds) or
gbrain doctor --json (parked_effects). Effects parked after repeated push
failures go through gbrain sources writer retry-effects <source> --parked-pushes
(preview with --dry-run); a conflict names the user's choice first.
Every run ends in exactly one of these. Successes exit 0; every failure prints
an [Error <code>] line (or a JSON envelope with --json) and exits non-zero.
Follow the error's fix.next as the
agent operator protocol describes.
| Outcome or code | Exit | What the agent does next |
|---|---|---|
pushed |
0 | Done. Report the commit; a hosted link to the file works now. |
push_deferred |
0 | Only with --require-clean (the post-commit hook): another push was running in this checkout and was asked to publish HEAD too when it finishes. Nothing to do. |
nothing_to_commit |
0 | The paths had no change. Any earlier local commit on the branch was still pushed, so nothing is waiting (with no_remote: true, nothing is pushed). |
committed_local |
0 | A managed canonical worktree, or a checkout with no Git remote (no_remote: true): the paths were committed locally and nothing was pushed. Report the commit; it reaches a remote on the owner's next publish, or after a remote is added and --push-only runs. |
pushed_checkout_lagging |
0 | The push landed but this checkout was not advanced; a [gbrain notice] names the files that blocked it. Change nothing: the next commit-push or --pull catches the checkout up. Never stash or reset to "fix" it. |
commit_lock_busy |
1 | Another writer held the lock for the whole wait; nothing was committed. Retry once. If it is still busy, tell the user which checkout and lock path the message names and stop. |
commit_paths_invalid |
1 | No paths, a path outside the checkout, the checkout root itself, or a path git would not stage. Name the explicit files you wrote and rerun. |
commit_prestaged_paths |
1 | Another writer staged files under a directory you named; nothing was staged or committed. Name your own files explicitly. Unstage the others (git restore --staged -- <file>, which keeps their bytes) only after the user agrees. |
commit_outside_source_root |
1 | The commit, or a local commit about to be published, changes files outside the brain's source root; nothing was pushed. Show the user git show --stat <sha>; rerun with --allow-outside-source-root only if they want it published. |
git_index_locked |
1 | A git command that does not take the writer lock held index.lock. Retry once after a few seconds. |
git_index_stale |
1 | An index.lock older than 10 minutes blocks the checkout. Check that no git command is running there, tell the user, and remove the named lock file only with their agreement. |
git_operation_in_progress |
1 | A rebase, merge, cherry-pick or revert is unfinished in the checkout. gbrain never aborts it for you. Follow stuck rebase. |
git_detached_head |
1 | The checkout is on a detached HEAD or an unborn branch. Check out the branch the remote tracks (git switch <branch>), or make the first commit, then rerun. |
integrate_conflict |
1 | Local and remote commits changed the same file; nothing was merged and the checkout is unchanged. Stop, relay the question to the user, then resolve. |
push_unconfirmed |
4 | The commit is durable locally but no push destination confirmed it (network, credentials, branch protection, a remote that kept moving, or another push that held the push lock for the whole wait). Fix what the message names, then run gbrain git commit-push --push-only. Never force-push. |
writer_coordinator_required |
1 | This is a managed worktree. Stop using git for pages; save them through put_page and the other gbrain write tools. |
git_unavailable |
1 | A git command failed, often a missing user.name/user.email or a refusing pre-commit hook. Read the message, fix that, rerun. When the branch names a remote that does not exist (a typo or a broken clone), the run stops before committing: fix the name or add the remote, then rerun. A checkout with no remote at all commits locally instead (committed_local). |
--pull reports up_to_date, advanced, ahead (local commits not yet
pushed; run --push-only), skipped_dirty (only with --require-clean) or
lagging (a dirty file the pull must change blocked it; a notice names it).
All of these exit 0. A diverged branch whose two sides changed the same file
fails with integrate_conflict, the same as a push.
The helper's own exits: 2 for a usage error (no message, or no paths: it refuses a repo-wide commit), 4 when its no-gbrain fallback could not push.
With --json, a success prints outcome, sha (the commit now on the
remote), committed (your commit, or null), branch, remote, paths,
integrated (remote commits merged in), checkout (advanced, unchanged
or lagging), lagging_reason when it lags, and repo.
Every run also appends a line to ~/.gbrain/brain-push.log, which
put_page's push status and gbrain doctor read. An unresolved
integrate_conflict is also recorded in the checkout itself
(git rev-parse --git-path brain-integrate-conflict.json), so put_page
reports it for that checkout until a publish resolves it.
A brain can be one folder inside a larger repository. gbrain sources harden
records the source's folder in the checkout's local git config
(gbrain.sourceRoot, one value per source; nothing when the source is the
whole checkout). With a root recorded, gbrain git commit-push refuses to
commit a path outside it, and refuses to publish any local commit that changes
a file outside it (commit_outside_source_root), whoever made the commit.
That catches a commit made around gbrain, such as a cron's
git add <dir>/; git commit, which takes everything any writer had staged.
Merge commits are not judged: gbrain's in-memory merges bring remote files in.
gbrain's own page writes are always confined to their source's folder; the
one exception is the durability scaffolding gbrain sources harden commits at
the checkout root (the helper script and the agent instructions file).
Say to your agent: "commit-push refused a commit outside the source root. What is in it?"
The agent shows you git show --stat <sha> for each commit the error names.
If you want it published as it is, it reruns
gbrain git commit-push --push-only --allow-outside-source-root.
gbrain's own page writes (put_page, remember's facts fences, takes, timeline,
lint --fix) commit their one file under the commit lock on a hardened
checkout, waiting 2 seconds for it. They never commit without the lock, since
that races the lock holder on the shared index. When another writer holds the
lock longer, or git refuses the commit, the file is written but not committed,
and gbrain notes it in a pending ledger next to the lock
(git rev-parse --git-path brain-writethrough-pending.log). The next page
write that gets the lock commits the pending files with its own, and
gbrain git commit-push --pending commits and pushes them on demand.
gbrain doctor reports pending files older than 30 minutes as dirty_age.
On a managed brain the persistence owner is the only Git writer in the
canonical checkout, and gbrain sources refresh is the only way upstream
commits come in. Four rules keep that checkout able to move:
- Scripts commit through gbrain, never with plain
git commit. A cron job or hook that writes files into the checkout and commits them with plain git leaves commits the upstream lacks and nobody publishes. The checkout diverges, and every later refresh refusesrefresh_divergedand changes nothing. Commit the files a script wrote withgbrain git commit-push -m "<msg>" -- <paths>: on a managed worktree it commits only those paths under the commit lock (committed_local) and the owner publishes them on its next Git publish. Pages can also go throughput_page,edit_page,rememberorcapture. CLI calls go through the one wrapper. - Refresh on a schedule.
gbrain sources refresh <id>drains queued writes, refuses new ones until it finishes, fast-forwards withgit merge --ff-onlyand re-syncs every source on the checkout; run it from cron on the owner host (cron line).refresh_divergedmeans something committed outside gbrain:git log --format='%h %an %ad %s' @{upstream}..HEADlists the stray commits. Fix that writer first, then, after the user agrees, publish the stray commits merged with the upstream (gbrain git commit-push --push-only --keep-checkout --path <checkout>) and retry the refresh, as the refusal says. Never force the checkout. - Stop syncing a source before rewriting its history. Before a
force-push or
git filter-repo, rungbrain sources set-sync <id> off(with the user's agreement) so automatic sync skips it, andgbrain sources mirror-readonly <id>so no managed write touches its checkout. A sync whose recorded base commit no longer exists makes sync and the maintenance cycle re-read the whole tree. After the rewrite, move the checkout onto the new history (upstream rewritten), thengbrain sources mirror-writable <id>andgbrain sources set-sync <id> on. - Decide what a gitignored path is. A page whose file a
.gitignorerule excludes cannot be committed, so its Git effect parks at once asgit_target_ignorednaming the rule (since v0.60.427.0). Either un-ignore it (remove or narrow the rule, commit that, thengbrain sources writer retry-effects <source> --parked-code git_target_ignored) or keep those pages database-only and leave the effects parked. Ask the user which; it decides whether those pages are in the Git backup.
A conflict means this machine's unpushed commit and the remote both changed the same file. On git 2.38 and later that means overlapping changes; on older git (Ubuntu 22.04 ships 2.34) any change to the same file on both sides counts as a conflict, even in different paragraphs.
Nothing changes when it happens: the local commit stays local, the checkout is
untouched and nothing is pushed. The error names the files, the local and
remote commits (in its detail), and a question to relay to the user. Until it
is resolved, every later commit-push from this checkout hits the same
conflict, even for other files, because the unpushed commit is still waiting.
One kind of conflict settles itself on push. When the remote's version of a
file is one this checkout already had at that path and later replaced with
another version (a stale clone or writer pushed an old copy back, often inside
a merge), gbrain keeps this machine's version: the remote's copy is history
this checkout already moved past. It looks at the last 200 commits that
changed the file. The push lists those files in superseded_remote_paths
(--json) and a [gbrain notice] line, and the merge commit message names
them. A version this checkout never had, a file the remote deleted, and a file
you name with --resolve stay ordinary conflicts. When both kinds meet in one
push, the error lists only the real conflicts (its detail.superseded names
the others), and the --resolve run keeps this machine's version of the
superseded files. --pull still reports them as conflicts; a
gbrain git commit-push --push-only settles them.
To resolve:
-
Ask the user which version of each file should win, or whether they want the two merged by hand. Choosing a side discards the other side's change to that file, so never pick one yourself.
-
Look at both versions if it helps the user decide:
git show HEAD:<path>(this machine) andgit show origin/<branch>:<path>(the remote). -
Rerun with
--expect-remote <remote sha>(copied from the error) and one--resolveper conflicted file, using the path exactly as the error printed it (relative to the checkout root; the error shell-quotes names with spaces or non-ASCII characters):# Keep this machine's version gbrain git commit-push --push-only --expect-remote 1a2b3c4d5e6f --resolve people/alice-example.md=ours # Keep the remote's version gbrain git commit-push --push-only --expect-remote 1a2b3c4d5e6f --resolve people/alice-example.md=theirs # Use a hand-merged copy written OUTSIDE the checkout gbrain git commit-push --push-only --expect-remote 1a2b3c4d5e6f --resolve people/alice-example.md=file:$HOME/.gbrain/merge-alice-example.md
It builds the same in-memory merge with your choice for each file, pushes it and advances the checkout. A conflicted file left without a
--resolvefails again with every conflicted file listed. If the remote moved since the user chose, it reports the conflict again against the new remote commit instead of applying the choice to a version nobody saw. Delete the merged copy afterwards.A file/directory or rename conflict (one side has
topicas a file, the other as a folder) cannot be settled by picking a side per file, so--resolverefuses it. Move or remove the local path in a new commit (gbrain git commit-push -m "<msg>" -- <paths>), then rungbrain git commit-push --push-only.
Write a merged copy outside the checkout: editing the conflicted file in place leaves it dirty, so the checkout cannot fast-forward over it afterwards.
On a managed worktree the owner's Git effects hold with integrate_conflict
instead (the database already has every page write; only the Git backup
waits). The fix the owner reports adds --keep-checkout and the checkout's
--path; after the choice is pushed, run
gbrain sources writer retry-effects <source> --parked-pushes for the effects
that parked meanwhile, then gbrain sources refresh <source> to bring the
merged remote files in.
Shared append-only files (a log.md every workflow appends to) are the usual
source of conflicts: two appends at the end of the same file overlap. Keep
both sides by merging by hand with file:, or make such files derived instead
of hand-appended.
A checkout where no one is around to answer the conflict question (an unattended agent or a cron) can opt in to quarantine instead:
git -C <checkout> config gbrain.quarantine trueWith it on, a content conflict no longer stops the push. gbrain publishes the
remote's version of each conflicted file and every non-conflicting change from
this machine, and keeps this machine's version under a ref named
refs/gbrain/quarantine/<time>-<commit>. Nothing is dropped: the parked
commit is also part of the published history. commit-push prints a
[gbrain notice] quarantine line (the quarantined field in --json), and
gbrain doctor reports the waiting ref as quarantined_changes until the
user is done with it.
Say to your agent: "What did gbrain quarantine?"
The agent shows you the doctor finding and the difference
(git diff <remote sha> <ref> -- <path>). Whatever should stay, it commits
again with gbrain git commit-push -m "<msg>" -- <path>; when you're
done, it deletes the ref with git update-ref -d <ref>.
File/directory and rename conflicts are never quarantined; they stop the push as described above. Quarantine is off by default.
An older helper or agent that ran git pull --rebase --autostash in the shared
checkout and crashed leaves .git/rebase-merge (or .git/rebase-apply)
behind. gbrain then refuses with git_operation_in_progress. The autostash
inside that directory can hold other writers' uncommitted edits, so save it
before anything else. Never delete the directory first.
-
Make sure the rebase is not still running: no
git rebaseorgit pullprocess in that checkout (ps -ef | grep '[g]it '). -
Save the autostash to the stash list, where it survives everything below:
cd <brain-checkout> for d in rebase-merge rebase-apply; do f="$(git rev-parse --git-path "$d/autostash")" [ -f "$f" ] && git stash store -m "autostash rescued from a stuck $d" "$(cat "$f")" done git stash list
-
End the rebase.
git rebase --abortreturns the branch to where it was before the rebase started, so local commits are kept. If it refuses, usegit rebase --quit, which leaves HEAD, the index and the working tree as they are. -
Check whether the edits are back:
git rebase --abortre-applies the autostash when it can, sogit statusmay already show them. If so, the stash entry is only a spare copy. If not, show the user the rescued edits (git stash show -p stash@{0}) and ask before putting them back. With their agreement:git stash apply stash@{0}The writers those edits belong to commit them as usual, or you commit them withgbrain git commit-push -m "<msg>" -- <paths>if the user asks. -
Bring the checkout up to date:
gbrain git commit-push --pull.
Each one moves or captures files other writers are using:
git pull --rebase, and any pull with--autostash: stashes every writer's edits for as long as the rebase runs. Use--pullinstead.git stashin any form other thangit stash store,listorshow: hides other writers' edits.git add -A,git add .orgit commit -awithout explicit paths: commits other writers' half-written files. Name your paths.git reset --hard: discards other writers' edits.git push --force: deletes other writers' published commits.- A plain
git push: not destructive, but it does not take the writer lock or integrate a rejected push. Use--push-only. - Deleting
.git/rebase-mergebefore its autostash is saved (stuck rebase).
gbrain sources harden also sets rebase.autoStash=false, merge.autoStash=false
and pull.ff=only in the checkout's local git config, so a stray git pull
refuses instead of autostashing.
The repository lint bun run check:shared-tree-git fails a gbrain skill,
recipe, template or doc that teaches any of these in a code block or an inline
code span. A line that must name one (a warning, or a single-writer clone such
as a fresh export) carries # gbrain-allow-shared-tree-git: <reason> on the
same line inside a code block, or <!-- gbrain-allow-shared-tree-git: <reason> -->
on the line before it in prose. A list of them, like the one above, sits
between <!-- gbrain-allow-shared-tree-git-begin: <reason> --> and
<!-- gbrain-allow-shared-tree-git-end -->. git push --dry-run is read-only
and never flagged.
A process outside gbrain that commits or pulls the same checkout (a host
git-sync service, your own cron script, an OpenClaw host's sync) joins the
protocol by taking the same lock. The simplest way is to call
gbrain git commit-push --pull and gbrain git commit-push -m "<msg>" -- <paths>,
which take it for you. A daemon that runs git itself follows this contract:
- Lock file:
git rev-parse --git-path brain-commit.lock(the commit lock), one per checkout (each linked worktree has its own). The lock is an exclusive OS file lock, the same oneflock -xtakes on Linux and macOS. The file existing means nothing; never delete it. - Legacy lock: helpers and hooks from before this protocol take
gbrain-push.lockin the same directory. gbrain takes both, alwaysbrain-commit.lockfirst; a daemon that may share a checkout with an old helper does the same, in the same order. - Hold it for local changes only: staging, committing, and moving the
checkout. Release it before pushing and push under
brain-publish.lock, as gbrain does. A daemon that pushes while holding the commit lock cannot make gbrain lose or revert its commits, but every gbrain writer waits for it:commit-pushfailscommit_lock_busyafter its wait, page writes leave their files pending, and gbrain's publisher reportspushed_checkout_laggingbecause it cannot advance the checkout. Its own plain push also loses races to gbrain's merged pushes and strands its commit untilgbrain git commit-push --push-onlyruns. - Commit with explicit paths (
git commit --only -- <paths>). A commit without paths takes everything any writer has staged in the shared index. - Mark your children: while holding the lock, set
BRAIN_COMMIT_LOCK_HELDto the lock file's absolute path in the environment of anything you start that may take it, such asgbrain git commit-pushor agit commitwhose post-commit hook runs gbrain. gbrain honors the marker only when it names this lock and the lock really is held, so a stale, copied or generic (1) marker cannot skip the lock. - Wait time: gbrain waits
GBRAIN_COMMIT_LOCK_WAIT_SECONDS(aliasGBRAIN_PUSH_LOCK_WAIT_SECONDS), default 60, then fails withcommit_lock_busy. Wait the same way and fail loudly; never skip silently. A push waitsGBRAIN_PUBLISH_LOCK_WAIT_SECONDS(default 300) for another push in the same checkout, then fails withpush_unconfirmed. - Inside the lock: integrate with
git fetchplusgit -c merge.autoStash=false merge --ff-only, or callgbrain git commit-push --pull; never rebase, stash or reset the shared tree.
cd <brain-checkout>
exec 8>"$(git rev-parse --git-path brain-commit.lock)"
flock -w "${GBRAIN_COMMIT_LOCK_WAIT_SECONDS:-60}" 8 || { echo "commit_lock_busy" >&2; exit 75; }
exec 9>"$(git rev-parse --git-path gbrain-push.lock)"
flock -w "${GBRAIN_COMMIT_LOCK_WAIT_SECONDS:-60}" 9 || { echo "commit_lock_busy" >&2; exit 75; }
export BRAIN_COMMIT_LOCK_HELD="$(git rev-parse --path-format=absolute --git-path brain-commit.lock)"
gbrain git commit-push --pull
gbrain git commit-push -m "host sync: daily notes" -- daily/gbrain git guard --install adds a local, untracked prepare-commit-msg hook
to the checkout. It refuses every commit that gbrain did not make under the
commit lock: a plain git commit, -a, --amend, and --no-verify too,
because that flag skips only the pre-commit and commit-msg hooks. A refused
commit changes nothing; whatever its writer staged stays staged. gbrain's own
commits (commit-push, page writes, the helper, including its no-gbrain
fallback) pass because they name the lock they hold. A merge commit, and a
commit that finishes a merge, cherry-pick, revert or rebase, pass too:
refusing those would leave the operation half done in the shared tree.
gbrain git guard prints whether it is installed, and
gbrain git guard --uninstall removes it. It refuses to replace another
tool's prepare-commit-msg hook and needs git 2.31 or later. It is off
unless you install it.
Say to your agent: "Stop crons and other scripts from committing to my brain around gbrain."
Your agent runs gbrain git guard --install in the brain checkout.
A person who needs a plain commit once bypasses it for that one command:
GBRAIN_COMMIT_GUARD=off git commit -m "fix the README by hand" -- README.md- Two agents editing the same file at the same time still conflict. The
lock serializes commits, not edits: if two writers change one file, the
second commit carries whatever is on disk when it commits, and edits on two
machines meet as an
integrate_conflict. When agents routinely write the same pages, use managed mode, where pages go throughput_pageand one persistence owner (topologies). - git older than 2.38 has no
merge-tree --write-tree, so any file changed on both sides is a conflict, even when the changes do not overlap. - A lagging checkout is not repaired automatically while another writer's
edit blocks it. The next commit-push or
--pullafter that edit is committed brings it current. - Writers that skip the lock (a hand-run
git commit) are not excluded unless the commit guard is installed. They cannot revert a locked writer's commit, because the checkout only fast-forwards, but a commit without paths can still sweep up staged files; with a source root recorded, gbrain refuses to publish one that reaches outside it.gbrain doctorreports such unpushed commits (commits_outside_source_root), a checkout behind its upstream for over an hour (checkout_lagging), pending or long-staged files (dirty_age), and local versions the opt-in quarantine parked (quarantined_changes). - No page-aware merge. Two edits to the same page meet as a text conflict; a merge driver that understands page sections is a planned follow-up.