fix(guard): steer live-checkout block message to disk-backed scratch clones
The guard's "use a separate worktree or temporary clone" advice sent agents to /tmp by default. /tmp is RAM-backed tmpfs on most distros, and parallel salvage clones each running npm ci (~1.6GB per clone) filled a 32GB tmpfs to 97% during a 15-subagent campaign, ENOSPC-ing sibling test runs. The message now recommends `git clone --shared <root> ~/.hermes/scratch/<task>` (honoring HERMES_HOME), warns that dependency installs belong on real disk, and tells the agent to delete the clone once the branch is pushed.
This commit is contained in:
@@ -354,3 +354,28 @@ class TestUnparseableCommands:
|
||||
def test_subshell_syntax_does_not_crash(self, repo):
|
||||
hit, _ = _detect("VAL=$(git rev-parse HEAD) git checkout main", repo, repo)
|
||||
assert hit is True
|
||||
|
||||
|
||||
class TestBlockMessageGuidance:
|
||||
"""The block message must steer agents to a disk-backed scratch clone,
|
||||
not a bare "temporary clone" (agents defaulted to /tmp, which is tmpfs
|
||||
on most distros — parallel salvage clones running npm ci filled a 32GB
|
||||
tmpfs to 97% in one campaign)."""
|
||||
|
||||
def test_message_recommends_shared_clone_on_disk(self, repo):
|
||||
hit, msg = _detect("git rebase origin/main", repo, repo)
|
||||
assert hit is True
|
||||
assert "git clone --shared" in msg
|
||||
assert "scratch" in msg
|
||||
|
||||
def test_message_warns_against_tmp_for_dep_installs(self, repo):
|
||||
hit, msg = _detect("git rebase origin/main", repo, repo)
|
||||
assert hit is True
|
||||
assert "tmpfs" in msg
|
||||
assert "Delete the clone" in msg
|
||||
|
||||
def test_scratch_hint_honors_hermes_home(self, repo, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", "/custom/hermes-home")
|
||||
hit, msg = _detect("git rebase origin/main", repo, repo)
|
||||
assert hit is True
|
||||
assert "/custom/hermes-home/scratch" in msg
|
||||
|
||||
@@ -714,9 +714,22 @@ def detect_self_repo_git_mutation(
|
||||
|
||||
|
||||
def _block_message(operation: str, root: Path) -> str:
|
||||
scratch = _scratch_dir_hint()
|
||||
return (
|
||||
f"Blocked: `{operation}` would rewrite Hermes's live source checkout "
|
||||
f"({root}) and can mix module versions in this running process. "
|
||||
"Use a separate worktree or temporary clone. To change this checkout, "
|
||||
"stop Hermes, run the command externally, then restart Hermes."
|
||||
f"Use a separate worktree or a shared clone on real disk, e.g. "
|
||||
f"`git clone --shared {root} {scratch}/<task>` — avoid /tmp for "
|
||||
"clones that install node/python deps: /tmp is usually RAM-backed "
|
||||
"tmpfs and a few dependency installs can fill it and ENOSPC other "
|
||||
"work. Delete the clone when the branch is pushed. To change this "
|
||||
"checkout, stop Hermes, run the command externally, then restart "
|
||||
"Hermes."
|
||||
)
|
||||
|
||||
|
||||
def _scratch_dir_hint() -> str:
|
||||
"""Disk-backed scratch location suggested to agents for temporary clones."""
|
||||
hermes_home = os.environ.get("HERMES_HOME", "").strip()
|
||||
base = Path(hermes_home).expanduser() if hermes_home else Path.home() / ".hermes"
|
||||
return str(base / "scratch")
|
||||
|
||||
Reference in New Issue
Block a user