Skip to content

Commit cd00152

Browse files
authored
Merge pull request #551 from escoffier-labs/t3code/fix/541-env-prefix-harness-session
fix(verify): propagate harness session past extra env prefixes
2 parents e4fd843 + f817ea6 commit cd00152

2 files changed

Lines changed: 83 additions & 3 deletions

File tree

src/brigade/work_cmd/verification.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,13 @@ def _fingerprint_segment(hasher, label: str, data: bytes) -> None:
507507
hasher.update(str(len(data)).encode() + b":" + data)
508508

509509

510+
def _stamp_harness_session(receipt: dict[str, Any]) -> None:
511+
"""Record the caller's Claude harness session when BRIGADE_CLAUDE_SESSION is valid 16-hex."""
512+
claude_session = os.environ.get("BRIGADE_CLAUDE_SESSION")
513+
if claude_session and re.fullmatch(r"[0-9a-f]{16}", claude_session):
514+
receipt["harness_session"] = {"harness": "claude", "fingerprint": claude_session}
515+
516+
510517
def _tree_fingerprint(target: Path) -> str | None:
511518
"""Content hash of HEAD + tracked diff + untracked files. None outside git."""
512519
try:
@@ -563,9 +570,7 @@ def _run_verify_commands(
563570
"tree_fingerprint": _tree_fingerprint(target),
564571
"planned_commands": [shlex.join(c) if isinstance(c, list) else c for c in commands],
565572
}
566-
claude_session = os.environ.get("BRIGADE_CLAUDE_SESSION")
567-
if claude_session and re.fullmatch(r"[0-9a-f]{16}", claude_session):
568-
receipt["harness_session"] = {"harness": "claude", "fingerprint": claude_session}
573+
_stamp_harness_session(receipt)
569574
try:
570575
graph_delta_before = graphtrail_delta.capture_before(target, run_dir, timeout=graphtrail_timeout)
571576
except KeyboardInterrupt:
@@ -719,6 +724,7 @@ def _write_reused_receipt(
719724
"tree_fingerprint": fingerprint,
720725
"planned_commands": planned_display,
721726
}
727+
_stamp_harness_session(receipt)
722728
git = _receipt_git_snapshot(target)
723729
if git is not None:
724730
receipt["git"] = git

tests/test_work_cmd_verification.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import os
3+
import shlex
34
import sqlite3
45
import subprocess
56
import sys
@@ -241,6 +242,79 @@ def _init_verify_target_with_head(target):
241242
_init_git_repo_with_head(target)
242243

243244

245+
@pytest.mark.skipif(os.name != "posix", reason="requires POSIX shell env-prefix invocation")
246+
def test_verify_reused_receipt_stamps_harness_session_from_outer_env_prefix(tmp_target, monkeypatch):
247+
"""Regression #541: cache-hit receipts must stamp outer BRIGADE_CLAUDE_SESSION."""
248+
from brigade.claude_hooks.runtime import _session_fingerprint
249+
from brigade.work_cmd import verification
250+
251+
_init_verify_target_with_head(tmp_target)
252+
graphtrail_bin = str(tmp_target / "missing-graphtrail")
253+
monkeypatch.setenv("GRAPHTRAIL_BIN", graphtrail_bin)
254+
255+
fingerprint_a = _session_fingerprint("session-a-outer-prefix")
256+
fingerprint_b = _session_fingerprint("session-b-outer-prefix")
257+
verify_command = "true"
258+
target = str(tmp_target)
259+
brigade_cli = (
260+
f"{shlex.quote(sys.executable)} -m brigade work verify run "
261+
f"--target {shlex.quote(target)} --command {shlex.quote(verify_command)}"
262+
)
263+
subprocess_env = {**os.environ, "GRAPHTRAIL_BIN": graphtrail_bin}
264+
265+
case_a = subprocess.run(
266+
["/bin/sh", "-c", f"BRIGADE_CLAUDE_SESSION={fingerprint_a} {brigade_cli}"],
267+
cwd=tmp_target,
268+
env=subprocess_env,
269+
check=False,
270+
capture_output=True,
271+
text=True,
272+
)
273+
assert case_a.returncode == 0, case_a.stderr
274+
275+
case_b = subprocess.run(
276+
[
277+
"/bin/sh",
278+
"-c",
279+
f"BRIGADE_CLAUDE_SESSION={fingerprint_b} PY=/fake/path {brigade_cli}",
280+
],
281+
cwd=tmp_target,
282+
env=subprocess_env,
283+
check=False,
284+
capture_output=True,
285+
text=True,
286+
)
287+
assert case_b.returncode == 0, case_b.stderr
288+
289+
receipts = verification._verify_receipts(tmp_target)
290+
assert len(receipts) == 2
291+
reused = receipts[0]
292+
fresh = receipts[1]
293+
assert reused["reused_from"] == fresh["run_id"]
294+
assert fresh["harness_session"] == {"harness": "claude", "fingerprint": fingerprint_a}
295+
assert reused["harness_session"] == {"harness": "claude", "fingerprint": fingerprint_b}
296+
assert reused["planned_commands"] == [verify_command]
297+
298+
299+
def test_verify_reused_receipt_records_env_assignments_inside_command(tmp_target, monkeypatch):
300+
from brigade.work_cmd import verification
301+
302+
_init_verify_target_with_head(tmp_target)
303+
monkeypatch.setenv("GRAPHTRAIL_BIN", str(tmp_target / "missing-graphtrail"))
304+
command = f'FOO=bar BAZ=qux {sys.executable} -c "print(1)"'
305+
306+
assert verification.verify_run(target=tmp_target, commands=[command], timeout=60) == 0
307+
assert verification.verify_run(target=tmp_target, commands=[command], timeout=60) == 0
308+
309+
receipts = verification._verify_receipts(tmp_target)
310+
assert len(receipts) == 2
311+
reused = receipts[0]
312+
fresh = receipts[1]
313+
assert reused["reused_from"] == fresh["run_id"]
314+
assert reused["commands"][0]["env"] == ["BAZ", "FOO"]
315+
assert reused["planned_commands"] == [command]
316+
317+
244318
def test_verify_reuses_identical_tree(tmp_target, monkeypatch):
245319
from brigade.work_cmd import verification
246320

0 commit comments

Comments
 (0)