git-hook — auto fetch/pull/commit/push for every worktree Hermes touches
A Hermes Agent plugin (hooks only, no tools) that keeps the git worktrees the agent works in honest:
- Before a read: at most one
git pull --ff-onlyper worktree per session (a pull fetches as part of the pull), and only when the worktree is clean and has an upstream. Nothing runs on a dirty worktree, so the agent reads current code instead of a stale checkout. - After the turn: commit only the files this turn changed and push. It does not sweep up unrelated dirty files, and it does not commit the agent's own secrets, state, caches or logs.
Failure is not silent, and it is not published a second way: a busy index, a hook rejection, a failed or timed-out push is written to the log, the paths stay queued for a retry, and the plugin also returns a note from its turn-end and session-end hooks. Hermes currently discards hook return values, so the log is where those notes actually surface.
State is per session: two sessions in one repository never share a batch, so one session's flush cannot publish the other session's half-finished edit.
Install
hermes plugins install <owner>/hermes-git-hook
hermes plugins enable git-hook
Manual install: copy this directory into ~/.hermes/plugins/git-hook and add
git-hook to plugins.enabled in config.yaml.
Configuration
Every knob is an environment variable, read per call — no config.yaml schema.
| Variable | Default | Meaning |
|---|---|---|
GIT_HOOK_ROOTS |
$PROJECTS_ROOT |
Colon-separated allowlist of directories whose worktrees are synced (opt-in per repo). A repo outside every entry is never pulled, committed or pushed. * means every worktree. |
GIT_HOOK_COMMIT |
1 (on) |
0 disables all commits and pushes. Reads still pull. |
GIT_HOOK_PUSH |
1 (on) |
Push is on for every allowlisted repo. 0 keeps commits local. |
GIT_HOOK_COMMIT_MSG |
update <name> |
Overrides the commit subject. |
GIT_HOOK_COMMIT_PATH |
– | Extra PATH entries (colon-separated) for git subprocesses, e.g. a credential helper. |
GIT_HOOK_PULL_TIMEOUT_S |
12 |
Per-worktree pull timeout. |
GIT_HOOK_HOOK_BUDGET_S |
20 |
Total budget for one pre_tool_call callback across every root it touches. The host allows 30 s before it fails the hook closed and blocks the tool call, so this stays under it. A root that would overrun is skipped and retried on the next call. |
GIT_HOOK_PUSH_TIMEOUT_S |
20 |
Per-worktree push timeout. |
PROJECTS_ROOT |
$HERMES_HOME/projects |
Treated as the agent's own workspace: never skipped by the secret rules. |
HERMES_HOME |
$HOME/.hermes |
Root of the agent's state; $HERMES_HOME/plugins is never synced. |
Falsy values are 0, false, no, off and the empty string.
What it will never do
- Touch a repo outside
GIT_HOOK_ROOTS(default: onlyPROJECTS_ROOT). - Run a repo's
core.fsmonitorcommand: every git call passes-c core.fsmonitor=. - Stage a secret-looking file in any repo:
.env*,auth.json,credentials*,id_rsa*/id_dsa*/id_ecdsa*/id_ed25519*,*.pem,*.key. The filter runs on every path beforegit add, and untracked directories are listed file by file (-uall), so a collapseddir/entry is never staged whole. - Touch anything under
/nix,/proc,/sys,/dev,/run,/tmp,/var/tmp. - Stage the agent's own state:
credentials,secrets,mcp-tokens,sessions,memories,state,hmc_state,logs,cache,cost-snapshots, orauth.json,config.yaml,.env,*.dbdirectly under$HERMES_HOMEor$HERMES_HOME/profiles/<name>/. - Sync the plugin install tree (
$HERMES_HOME/plugins/**). A catalog install is a single-commit checkout pinned to the SHA that was reviewed; this plugin will not pull or commit it away from that commit. - Run git under its module lock. One session's slow pull cannot make another
session's
post_tool_callcallback look "still running" and get skipped.
Tests
python -m pytest tests/ -q
The suite is in-process: it builds throwaway repositories under a temp dir and
drives the real code paths (_skipped, commit_and_push, _push, the
transient-path filter). No network access.
License
MIT — see LICENSE.