release(v4.9.1): tests no longer reach a real herdr server - archive the review reports and bump the lockstep version
Review loop (--review-only, --all-reviewer): round 1 (@325b056) 4/4 PASS,
round 2 (@db7f4ca, HERDR_SESSION) 4/4 PASS. Full suite at 325b056 on
macOS / Python 3.9 / bash 3.2 / herdr 0.9.3 from an agent pane:
1845 passed / 0 failed / 56 skipped (18 test_deploy_freshness skips: no
nats-docker submodule in the fresh worktree); the panes of the real
servers (default, multi-agent-mux) and ~/.claude/projects were unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
db7f4cab9c
commit
50c8da8269
32 files changed
+607
-26
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T001819Z-131fa6",
|
||||
"base_sha": "8b0b4a07dc76a4915895bdde6170dc8eed9b5f74",
|
||||
"head_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"tree_sha": "7905896f005159d596e3872d0ee2c3c12061f09a",
|
||||
"review_commit": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"reviewer": "reviewer-grok-01",
|
||||
"job_id": "191f0ec3",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T00:57:46Z"
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Review 191f0ec3 — a test run from a pane no longer opens workspaces on that pane's server
|
||||
|
||||
Head `325b056057fcdd717d2c81017fc5ef57183cb4d0` (tree `7905896f005159d596e3872d0ee2c3c12061f09a`) is two commits on base `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74`: `525df2b` (the leak fix) and `325b056` (the generated-shim pin moves to `525df2b`). Four files, +106/−5. `git diff --check` is clean. Snapshot porcelain empty. Control-plane main is still `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74`, porcelain empty.
|
||||
|
||||
Both measured causes are right, and the fix matches them.
|
||||
|
||||
The suite that left seven workspaces on `multi-agent-mux` ran with `--basetemp .../pytest-pr56-shim`. `mock_herdr` puts its `bin/herdr` under that basetemp. Both resolvers used to skip any PATH directory whose path contained `-shim` anywhere, so they walked past the mock and executed `/opt/homebrew/bin/herdr`. The real binary follows `HERDR_SOCKET_PATH` ahead of `HOME`, and `mam_sandbox` left the pane's socket in the environment.
|
||||
|
||||
`_is_shim_path` (`lib.sh:113-125`) still matches `/.mam/shim/`, `/multi-agent-herdr-shim/`, and `/.agents/skills/.bin`. The substring `*-shim/*` is gone. A second `case` matches only when the path with one trailing slash stripped is itself `*-shim`. `_resolve_real_herdr_path` (`:127-143`) skips a PATH directory on that test, then skips the candidate when `_is_shim_path` matches `dirname` of the canonical file (`:136`). It still prints the PATH entry, `$dir/herdr`. The generated shim's `_resolve_real_herdr` (`:172-186`) now requires `${dir%/}` to fail the pattern `*-shim`, and it still rejects a directory containing `.mam/shim`.
|
||||
|
||||
`mam_sandbox` deletes `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, and `HERDR_TAB_ID` (`tests/conftest.py:296-298`).
|
||||
|
||||
## Wrapper directories
|
||||
|
||||
A decoy-only probe sourced this snapshot's `lib.sh` with `WORKSPACE_ROOT=/tmp/m191ws`. PATH was the case directory, a stand-in, `/usr/bin`, and `/bin`. The real binary was not on it.
|
||||
|
||||
| PATH directory | `_resolve_real_herdr_path` | generated shim |
|
||||
|---|---|---|
|
||||
| `.../pytest-x-shim/case0/bin` | that mock | that mock |
|
||||
| a directory whose own name is `herdr-shim` | stand-in | stand-in |
|
||||
| `.../multi-agent-herdr-shim/srv` | stand-in | that directory |
|
||||
| `.../.agents/skills/.bin` | stand-in | that directory |
|
||||
| `.../.mam/shim/extra` | stand-in | stand-in |
|
||||
|
||||
An ancestor named `pytest-x-shim` above `bin/` is selected by both resolvers. A directory that is itself `*-shim` is skipped by both. `.mam/shim` and a child of it are skipped by both. L1 and L1b assert the same two outcomes inside the suite, with a decoy behind the mock, and both passed.
|
||||
|
||||
`lib.sh` still excludes `/multi-agent-herdr-shim/` and `/.agents/skills/.bin`. The generated shim excludes a PATH entry whose own name ends in `-shim`, so the directory `multi-agent-herdr-shim` itself is skipped by both. A child of that directory, whose own name does not end in `-shim`, is excluded only by `_resolve_real_herdr_path`. `README.md:102` and `README.ko.md:84` still describe `/tmp/multi-agent-herdr-shim/<HERDR_SERVER_NAME>/herdr`. No script in this tree creates that path; `_init_herdr_isolation` writes `$WORKSPACE_ROOT/.mam/shim/herdr`. The `.agents/skills/.bin` gap on the generated shim is older than this commit: the previous generated check was the substring `*"-shim"*`, and `.bin` never matched it. Nothing in `lib.sh` prepends `.agents/skills/.bin` onto PATH.
|
||||
|
||||
## What still reaches the real binary
|
||||
|
||||
A direct call of `mam_sandbox`, with probe values set first, left `HERDR_SESSION` and `HERDR_ENV` set and removed `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_SESSION_NAME`, `HERDR_WORKSPACE_ID`, and `HERDR_CONFIG_PATH`. `HOME` was the sandbox. L2's `PANE_VARS` (`tests/test_herdr_real_server_leak.py:74-75`) are the socket, the pane and tab ids, the workspace id, and `HERDR_SESSION_NAME`. The test passed. It does not include `HERDR_SESSION`.
|
||||
|
||||
This reviewer process inherited `HERDR_SESSION` from its pane. `lib.sh:1411-1415` records that herdr 0.8.2 left `HERDR_SESSION` unset on a default-server pane and exported the socket to every pane. `docs/contracts/issue-6/implementation_plan.md` records the measurement that the real binary follows `HERDR_SESSION`, `HERDR_SOCKET_PATH`, and `--session`, and that `HERDR_SESSION_NAME` alone reaches the default socket. The calls that created the seven workspaces go through these resolvers, which now return the mock, so they do not reach the binary that reads `HERDR_SESSION`. A later call that misses the mock still inherits `HERDR_SESSION`.
|
||||
|
||||
The suite's intentional real-server tests already drop it. `tests/test_issue45_restore.py` lists `HERDR_SESSION` in `SCOPE_ENV` and builds the server env from what remains. `tests/test_issue50_dispatch_herdr.py` `Box.env` drops every `HERDR_*` name, and `Box.herdr` passes only `HOME`, `PATH`, and `--session`. `tests/test_herdr_shim_contract.py::test_h10_real_herdr_schema_match` calls `herdr api schema` with the inherited environment and does not use `mam_sandbox`. That test was already in the tree. I did not run it, and I did not run the real binary. `api schema` is a read. The workspace-creating path was the resolver.
|
||||
|
||||
`HERDR_ENV` also survived the fixture call. The measured contract names `HERDR_SESSION`, `HERDR_SOCKET_PATH`, and `--session`. I did not treat `HERDR_ENV` as a second route.
|
||||
|
||||
## Shared control plane
|
||||
|
||||
`test_s11_this_branch_does_not_change_the_generated_shim` (`tests/test_issue23_24_startup_path.py:294-315`) compares the shim heredoc with `git show 525df2b:.agents/skills/lib.sh`, unless `MAM_BASE_SHA` overrides it. It passed, so the heredoc at `325b056` is the heredoc at `525df2b`. The second commit changes the pin and the comment. Sourcing `lib.sh` rewrites `$WORKSPACE_ROOT/.mam/shim/herdr`. This review sourced it with `WORKSPACE_ROOT` pointed at `/tmp/m191ws` and at pytest sandboxes. The narrowing becomes the control plane's shim when this branch is merged and something sources `lib.sh` with that workspace root.
|
||||
|
||||
## What was run
|
||||
|
||||
Control-plane venv, Python 3.9.6, pytest 8.4.2, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, `--basetemp=/tmp/m191bt-leak2` (the path contains neither `shim` nor `mam-review`). Inherited `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID`, `HERDR_SESSION_NAME`, `HERDR_SESSION`, `HERDR_SERVER_NAME`, `HERDR_CONFIG_PATH`, `HERDR_ENV`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, and `CODEX_HOME` were unset.
|
||||
|
||||
`tests/test_herdr_real_server_leak.py` and `tests/test_issue23_24_startup_path.py::test_s11_this_branch_does_not_change_the_generated_shim`: 4 passed in 1.50s. The session guards that travel with that run completed. The #55 agent-home guard did not fire.
|
||||
|
||||
User `~/.config/herdr/config.toml` mtime stayed `1784456394`. Snapshot porcelain stayed empty after the run.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T010208Z-f90dd4",
|
||||
"base_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"head_sha": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"tree_sha": "f32cabd3a6c8d775c9223509967f1a810f0cf827",
|
||||
"review_commit": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"reviewer": "reviewer-grok-01",
|
||||
"job_id": "9cd9b3f0",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T01:21:50Z"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Review 9cd9b3f0 — the sandbox also drops HERDR_SESSION
|
||||
|
||||
Head `db7f4cab9c60ff5db009cf3fc625e9a86a465ef6` (tree `f32cabd3a6c8d775c9223509967f1a810f0cf827`) is the one commit `db7f4ca test(sandbox): mam_sandbox also drops HERDR_SESSION` on base `325b056057fcdd717d2c81017fc5ef57183cb4d0`. Two files, +5/−1. `git diff --check` is clean. Snapshot porcelain empty. Control-plane main is still `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74`, porcelain empty.
|
||||
|
||||
`mam_sandbox` deletes `HERDR_SESSION` next to `HERDR_SOCKET_PATH` (`tests/conftest.py:298-299`). L2's pane model now includes `HERDR_SESSION=real` (`tests/test_herdr_real_server_leak.py:73-76`) and asserts that name is gone with the others (`:86-88`).
|
||||
|
||||
## What the variable does inside a sandbox
|
||||
|
||||
The herdr binary treats `HERDR_SESSION` as a session name: the string beside it is `session name cannot be empty`. A named session's socket is `$HOME/.config/herdr/sessions/<name>/herdr.sock`. `mam_sandbox` sets `HOME` to the sandbox, so that name resolves inside the sandbox. The route that reached the caller's real server was `HERDR_SOCKET_PATH`, which the previous commit already deletes.
|
||||
|
||||
`_herdr_caller_server` (`lib.sh:1424-1427`), `pane_lock.caller_server` (`lib_py/pane_lock.py:44-50`), and `watcher.caller_server` (`lib_py/loop/watcher.py:50-66`) return `HERDR_SESSION` whenever it is set, ahead of the socket. A test run inside a pane therefore took the in-pane branch, and the same test outside a pane took the other. Dropping the variable makes those helpers see a process outside a pane unless the test supplies the name itself.
|
||||
|
||||
## Tests that supply the name
|
||||
|
||||
No test calls `monkeypatch.setenv("HERDR_SESSION")`. The tests that need a value build the child environment themselves:
|
||||
|
||||
- `tests/test_issue23_24_startup_path.py` `_run_lib` (`:36-40`) pops `HERDR_SESSION` from the inherited environment and then applies the dict the test passes. `test_s1g` passes `HERDR_SESSION=from-herdr` and expects `caller=from-herdr`.
|
||||
- `tests/test_issue34_job_tree.py` `_run_lib` (`:590-591`) drops every `HERDR_*` name and then applies `SAME_SERVER_PANE`, which contains `HERDR_SESSION=multi-agent-mux`.
|
||||
- `tests/test_issue50_pane_lock.py` passes an explicit mapping into `lock_path` and into the `pane_lock path` subprocess. `test_p1b` builds that subprocess environment from the keys that do not start with `HERDR_`, then adds the socket it wants.
|
||||
|
||||
A direct call of `mam_sandbox` with `HERDR_SESSION=probe-session` left the name unset. A `setenv` after that call left `HERDR_SESSION=set-by-test`. The fixture setup runs before the test body, so a test that sets the name for itself keeps it. The one `setenv` of a `HERDR_` name in a test body is `HERDR_SERVER_NAME` in `tests/test_tier3_integration.py:31`, and that line runs after the fixture.
|
||||
|
||||
## What the sandbox still leaves
|
||||
|
||||
`HERDR_ENV` stays. In this pane its value is `1`. The binary's text says `Requires HERDR_ENV=1` beside the inception check, so this is a feature flag. `HERDR_BIN_PATH` stays, and its value names the herdr binary. `HERDR_CLIENT_SOCKET_PATH` sits beside `HERDR_SOCKET_PATH` in the binary's pane-export cluster. This process does not have that name, and the sandbox does not delete it.
|
||||
|
||||
## What was run
|
||||
|
||||
Control-plane venv, Python 3.9.6, pytest 8.4.2, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, `--basetemp=/tmp/m9cdbt`. Inherited `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID`, `HERDR_SESSION_NAME`, `HERDR_SESSION`, `HERDR_SERVER_NAME`, `HERDR_CONFIG_PATH`, `HERDR_ENV`, `HERDR_BIN_PATH`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, and `CODEX_HOME` were unset. The real herdr binary was not run.
|
||||
|
||||
`tests/test_herdr_real_server_leak.py`, `tests/test_issue23_24_startup_path.py`, and `tests/test_issue50_pane_lock.py`: 84 passed in 195.84s. That is the same 84 the orchestrator reported. The session guards on that run completed. The #55 agent-home guard did not fire.
|
||||
|
||||
User `~/.config/herdr/config.toml` mtime stayed `1784456394`. Snapshot porcelain stayed empty after the run.
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user