Fix #2684: root slice-BRC + context-PR temp worktrees under WORKTREE_BASE_DIR - #2690
Conversation
…BASE_DIR The slice-BRC and context-PR hooks built their temp worktrees with ``tempfile.mkdtemp(dir="/tmp")``, then pushed via ``gateway.push_worktree_branch(repo_path=str(wt_path), ...)``. The gateway's ``validate_repo_path`` only accepts paths under ``ALLOWED_REPO_PATHS`` (``/home/egg/repos/``, ``.egg-worktrees/``, ``.egg-state/``, ``/repos/``), so every push was rejected with ``repo_path must be within allowed directories``, the failure was swallowed at WARNING level, and slice PRs opened without their BRC consensus transcript (and context PRs without their refine/plan artifacts). Reparent both temp worktrees to ``WORKTREE_BASE_DIR`` (``/home/egg/.egg-worktrees``), which is already in the allowlist and already mounted by the spawner. Falls back to the system temp dir when the base path is absent (unit tests). Regression tests pin the contract: - The hook's captured ``repo_path`` must live under ``WORKTREE_BASE_DIR`` (catches future drift back to ``/tmp``). - ``WORKTREE_BASE_DIR`` must lie inside the gateway's ``ALLOWED_REPO_PATHS`` (catches drift on either side).
There was a problem hiding this comment.
Review summary
The fix is correct, surgical, and well-tested. Both _open_context_pr_for_pipeline and _commit_slice_brc_history_to_integration_branch were creating temp worktrees at /tmp/... and then handing that path back to gateway.push_worktree_branch, where validate_repo_path rejected it because /tmp is not in ALLOWED_REPO_PATHS. Rerooting tempfile.mkdtemp under WORKTREE_BASE_DIR (/home/egg/.egg-worktrees/) puts the path inside the gateway's allowlist and the push goes through. I verified the gateway's allowlist in gateway/git_client.py:178-183 and the orchestrator's WORKTREE_BASE_DIR at orchestrator/routes/pipelines.py:736 — they line up.
Scope check: grep -n 'tempfile.mkdtemp' in orchestrator/ returns only these two call sites, so this is the full extent of the bug. Other push_worktree_branch callers take a pre-existing worktree_repo_path (the gateway-provisioned per-pipeline worktree), which is already under WORKTREE_BASE_DIR.
Test coverage is good:
- The two behavioral tests pin that the
repo_pathkwarg actually passed to the gateway lives under the (monkeypatched)WORKTREE_BASE_DIR. test_production_worktree_base_dir_lies_within_gateway_allowlistimports the realvalidate_repo_pathand is the right kind of cross-module contract test — without it, a future refactor of the gateway allowlist would silently re-break this.
Locally: pytest orchestrator/tests/test_per_slice_brc_commit.py orchestrator/tests/test_context_pr.py → 60 passed. The new tests pass.
Approving. Two non-blocking notes below.
Non-blocking
1. test_production_worktree_base_dir_lies_within_gateway_allowlist only pins one side of the contract.
orchestrator/tests/test_per_slice_brc_commit.py:770-783 hardcodes /home/egg/.egg-worktrees/... in the candidate string rather than reading from WORKTREE_BASE_DIR. The docstring says "If either side drifts, this test catches the silent-push-rejection regression," but in practice only the gateway side is pinned — if someone changes WORKTREE_BASE_DIR in pipelines.py to a path outside the allowlist, this test still passes against the stale hardcoded prefix. Suggest:
from routes.pipelines import WORKTREE_BASE_DIR
from gateway.git_client import validate_repo_path
candidate = str(WORKTREE_BASE_DIR / "egg-slice-brc-pipeline-x-slice-y-abc" / "wt")
ok, error = validate_repo_path(candidate)
assert ok, f"WORKTREE_BASE_DIR drifted out of gateway ALLOWED_REPO_PATHS: {error}"2. The dir=None fallback silently re-introduces the original bug.
At orchestrator/routes/pipelines.py:10221 and :10986, when WORKTREE_BASE_DIR.exists() is False the code falls back to tempfile.mkdtemp(dir=None), which uses tempfile.gettempdir() → typically /tmp. The comment frames this as "unit tests," but in production the only reason WORKTREE_BASE_DIR.exists() would be False is that the docker volume mount is broken — and in that case we'd silently fall back to the exact path the gateway rejects, recreating #2684 without any signal. A logger.warning(...) on the fallback branch would make a real misconfiguration noisy. (Arguably the same condition makes the broader pipeline non-functional, so this is defense-in-depth, not a correctness issue — hence non-blocking.)
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…code contract test - Log a WARNING when WORKTREE_BASE_DIR is absent before falling back to the system temp dir. In production the only reason the base path would be missing is a broken docker volume mount, in which case the fallback would silently recreate the #2684 push-rejection; the warning makes that misconfiguration noisy. Applied to both call sites (context-PR hook + slice-BRC commit hook). - Rewrite test_production_worktree_base_dir_lies_within_gateway_allowlist to derive the candidate path from WORKTREE_BASE_DIR (orchestrator module attribute) instead of a hardcoded /home/egg/.egg-worktrees/ string. Now drift on the orchestrator side also trips the test, closing the asymmetric-coverage gap the reviewer flagged.
Review feedback addressedBoth non-blocking notes fixed in this PR. 1. 2. Verification:
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review summary
Both non-blocking concerns from my prior review are addressed correctly.
1. Bidirectional drift coverage — test_production_worktree_base_dir_lies_within_gateway_allowlist now reads WORKTREE_BASE_DIR from routes.pipelines (orchestrator/tests/test_per_slice_brc_commit.py:782-786) and builds the candidate path from it. An orchestrator-side path move (e.g. changing WORKTREE_BASE_DIR to a path outside the allowlist) now trips the test, not just gateway-side drift. Implementation matches what I suggested.
2. Fallback now noisy — both call sites (orchestrator/routes/pipelines.py:10223-10234 and :11001-11014) now emit a structured logger.warning(...) on the WORKTREE_BASE_DIR missing branch before tempfile.mkdtemp(dir=None). The wording calls out the production cause ("likely a broken volume mount") and the operator-visible impact ("push to the … branch will be rejected by the gateway allowlist"), with #2684 referenced inline. Structured kwargs are role-appropriate: the context-PR site logs pipeline_id + worktree_base_dir; the slice-BRC site additionally logs slice_id + integration_branch. Pattern matches existing structured-logging usage in the file (e.g. pipelines.py:466-480).
Cross-checked the contract still holds against production: WORKTREE_BASE_DIR = Path("/home/egg/.egg-worktrees") (pipelines.py:736) and ALLOWED_REPO_PATHS (gateway/git_client.py:178-183) includes /home/egg/.egg-worktrees/, so the candidate /home/egg/.egg-worktrees/egg-slice-brc-…/wt matches the prefix at validate_repo_path:241.
No new issues. Approving.
— Authored by egg
|
egg review completed. View run logs 3 previous review(s) hidden. |
Summary
_commit_slice_brc_history_to_integration_branchand_open_context_pr_for_pipelineboth built their temp worktree attempfile.mkdtemp(dir="/tmp"), then passedrepo_path=str(wt_path)togateway.push_worktree_branch. The gateway'svalidate_repo_pathrejects anything outsideALLOWED_REPO_PATHS(/home/egg/repos/,/home/egg/.egg-worktrees/,/home/egg/.egg-state/,/repos/), so every push failed withrepo_path must be within allowed directories. The failure was swallowed at WARNING level — slice PRs opened without their BRC consensus transcript and context PRs opened without their refine/plan artifacts.WORKTREE_BASE_DIR(/home/egg/.egg-worktrees), which is already in the gateway allowlist and already mounted by the spawner. Falls back to the system temp dir when the base path is absent (unit tests).repo_pathlives underWORKTREE_BASE_DIR, and (b)WORKTREE_BASE_DIRlies inside the gateway'sALLOWED_REPO_PATHS— catches drift on either side of the orchestrator/gateway contract.Closes #2684.
Test plan
make test(full suite, changeset-aware): 2760 passedpytest orchestrator/tests/test_per_slice_brc_commit.py::TestGatewayAllowlistCompatibility orchestrator/tests/test_context_pr.py::TestContextPRGatewayAllowlistCompatibility— 3 passedtest_per_slice_brc_commit.py+test_context_pr.pystill pass post-fix