fix(runtime): release and report failed background supervisor setup

This commit is contained in:
Alexandre Teixeira
2026-10-01 22:26:36 +01:00
parent 63fe66e85e
commit 0dec375631
2 changed files with 40 additions and 2 deletions
+9 -2
View File
@@ -57,6 +57,9 @@ async def supervise(payload: dict) -> None:
output = "\n…[output truncated by containment capture limit]…\n"
except BaseException as exc:
record = containment._load_records().get(grant.id, {})
if not record.get("pid") and not record.get("release"):
containment.release(grant, grace_s=0)
record = containment._load_records().get(grant.id, {})
output, code = f"background execution failed: {type(exc).__name__}: {exc}\n", 1
report = {"containment": grant.to_dict(), "teardown": record.get("release") or {"dead": False},
"output_truncated": False}
@@ -66,8 +69,12 @@ async def supervise(payload: dict) -> None:
if isinstance(exc, containment.ContainmentUnavailable):
report.update(containment.unavailable_tool_result(exc, tool="bash"))
if output:
with open(payload["log_path"], "a", encoding="utf-8") as log:
log.write(output)
try:
with open(payload["log_path"], "a", encoding="utf-8") as log:
log.write(output)
except OSError:
# A failed log initialization must not hide completion metadata.
sys.stderr.write(output)
atomic_write_json(payload["result_path"], report)
# Publish completion last: refresh must never see an exit without metadata.
atomic_write_text(payload["exit_path"], str(code if code is not None else 1))
+31
View File
@@ -52,6 +52,37 @@ def test_detached_execution_owns_boundary_and_reports_death(jobs):
assert result["teardown"]["dead"] is True
def test_supervisor_setup_failure_closes_unstarted_grant(jobs):
import json
import subprocess
import sys
from pathlib import Path
path, _ = jobs
spec = containment.agent_spec(str(path), dict(os.environ), 5)
grant = containment.acquire(spec, owner="failed-supervisor")
payload = {
"store_path": str(containment._store_path()),
"grant": {**grant.to_dict(), "owner": grant.owner},
"spec": {"workspace": str(path), "env": dict(spec.env), "wall_clock_s": 5,
"required": sorted(spec.required)},
"command": "printf effect > must-not-exist",
"log_path": str(path / "missing-directory" / "job.log"),
"result_path": str(path / "result.json"), "exit_path": str(path / "exit"),
}
worker = Path(containment.__file__).with_name("containment_worker.py")
result = subprocess.run([sys.executable, str(worker)], input=json.dumps(payload),
capture_output=True, text=True, timeout=10)
assert result.returncode == 0 # Supervisor publishes the failed job result.
assert "FileNotFoundError" in result.stderr
assert not (path / "must-not-exist").exists()
assert containment.active_grants() == []
assert (path / "exit").read_text() == "1"
report = json.loads((path / "result.json").read_text())
assert report["containment"]["executed"] is False
assert report["containment"]["contained"] is False
assert report["teardown"]["dead"] is True
async def test_bg_marker_refuses_without_spawning_and_authority_still_gates(jobs, monkeypatch):
path, _ = jobs
monkeypatch.setattr(containment, "CONTAINMENT_MODE", containment.MODE_ENFORCING)