feat(tools): block git mutations of the running source checkout#65791
Open
erosika wants to merge 4 commits into
Open
feat(tools): block git mutations of the running source checkout#65791erosika wants to merge 4 commits into
erosika wants to merge 4 commits into
Conversation
When hermes runs from a source/editable install, a git checkout/reset/pull in its own repo swaps code on disk under the live interpreter. Modules imported before the switch stay old while later lazy imports load new code, producing delayed signature TypeErrors and tracebacks that don't match the source, typically losing the in-flight turn. New tools/self_repo_guard.py detects working-tree/ref mutations (checkout, switch, reset, rebase, merge, pull, restore, stash, clean, cherry-pick, revert) whose target repo is the source root the process runs from, via cwd, git -C, cd chains, and subshell segments. Read-only git, commits, fetch, and git worktree add stay allowed; packaged installs (no .git) are inert.
Wire the self-repo guard in next to the gateway lifecycle hard-block, before the force check — force=True cannot make the command safe, only delay the crash. Local backend only: sandboxed backends cannot reach the host checkout. The block message explains the version-skew mechanism and redirects to git worktree add / a temp clone, or running the command outside hermes with a restart after.
The function-scoped import at the end of run_conversation loads agent.turn_finalizer fresh from disk on the first turn that reaches it. On a source/editable install whose checkout changed mid-session, that pairs an old caller with a new callee at the exact seam where every turn's work is persisted — the turn crashes on a signature mismatch after the work is done. The lazy import was never cycle-forced: turn_finalizer defers its own conversation_loop import.
tonydwb
reviewed
Jul 16, 2026
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved (read-only token — formal approval requires write access)
Blocks git mutations (checkout, pull, branch, stash, etc.) when the running source checkout is detected. Prevents accidental corruption of the running hermes-agent installation.
- New
tools/self_repo_guard.pymodule with path resolution and git command detection - Hoists the
finalize_turnimport to module level to avoid stale-module reads after git operations - Good test coverage in
tests/tools/test_self_repo_guard.py - No security issues found
Reviewed by Hermes Agent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In a source/editable install, changing Hermes's own checkout underneath the live interpreter can mix module versions: already-imported modules stay old while later imports load new code. The resulting failures surface well after the Git command and can discard a completed turn.
The observed failure happened after the agent checked out a PR branch in its running source repo. At turn end, the lazy
finalize_turnimport loaded the branch version under the old caller and raised a signatureTypeError.Fix
Live source-checkout guard
Local terminal commands now hard-block Git operations that can rewrite the checkout backing the running process. The guard:
git -C,GIT_WORK_TREE/--work-tree,cdchains, wrappers, nested shells, and Git aliases;gh pr checkoutandhub pr checkoutas well as direct Git commands;Read-only and non-worktree operations remain available, including normal status/log/diff workflows, commits, fetch/push, worktree creation, soft/mixed reset, staged-only restore, stash metadata operations, and clean dry-runs. The block cannot be bypassed with
force=True; approval cannot make an in-process code swap safe.Scope: this is a guardrail against the agent accidentally rewriting its own code, not a security boundary. A determined adversarial command can construct shell text the scanner cannot statically resolve; the failure mode it targets — and the one observed — is the model reaching for a plain
git checkout/gh pr checkoutin the wrong repo, and the block message redirects it togit worktree addor a temp clone.Shared scanner improvement
tools/approval.py's_iter_shell_command_startsnow recurses into nested$( )and backtick command substitutions instead of only recording the outermost start. The guard reuses this scanner, and existing dangerous-pattern detection benefits from the same nested coverage. No pattern or approval-flow behavior changes otherwise; covered by the approval-parser suite.Stable turn finalization
finalize_turnis bound whenagent.conversation_looploads instead of being imported for the first time after the agent loop. An out-of-band source change (e.g. the user runninggit pullin a separate shell mid-session, which no command guard can see) can therefore no longer load a skewed finalizer at the persistence seam.Validation
tyclean for the changed guard code