From 1bf45b9fed2484072b9902fd516b28adddaf04bd Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Fri, 2 Oct 2026 02:23:07 +0100 Subject: [PATCH] fix(runtime): reconcile lifecycle CI contracts - Regenerate website/configuration-reference.md: Wave 5B moved the ODYSSEUS_BROWSER_SCREENSHOT_DIR read in web_tools.py (3458 -> 3479). - Give the Chrome sweep regression fixture a real process identity (stat start time, boot id, process_ownership.PROC_ROOT). The sweep now signals only verified identities; the old cmdline-only fixture borrowed the identity of whatever real process held pid 101 on the host, so it passed or failed depending on the machine. - Import pytest in test_workspace_artifact_tool_floor.py: its existing bubblewrap capability skip raised NameError on hosts without functional namespaces. No production code changes. Required containment still fails closed. --- tests/test_runtime_behavior_regressions.py | 21 ++++++++++++++++++++- tests/test_workspace_artifact_tool_floor.py | 2 ++ website/configuration-reference.md | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/test_runtime_behavior_regressions.py b/tests/test_runtime_behavior_regressions.py index 9563bb177..1b4d28b2d 100644 --- a/tests/test_runtime_behavior_regressions.py +++ b/tests/test_runtime_behavior_regressions.py @@ -243,6 +243,9 @@ def test_native_terminal_runtime_adds_offered_tools_deliberately(): # belonging to the user, or to another worktree, must survive it. def test_chrome_sweep_kills_only_this_runtimes_profile(monkeypatch, tmp_path): + import shutil + + from src import process_ownership from src.agent_tools.web_tools import PrivateBrowserTool proc = tmp_path / "proc" @@ -250,9 +253,19 @@ def test_chrome_sweep_kills_only_this_runtimes_profile(monkeypatch, tmp_path): tmpdir.mkdir() ours = str(tmpdir.resolve() / "agent-browser-chrome-") + # A fake process needs an identity, not only a command line: the sweep + # signals a process only after verifying the start token it matched on. + # Without a stat here the token would be read from the host's real /proc, + # so the outcome would depend on whether this pid happens to exist. + boot = proc / "sys/kernel/random/boot_id" + boot.parent.mkdir(parents=True) + boot.write_text("fake-boot\n") + def _pid(pid, cmdline): entry = proc / pid entry.mkdir(parents=True) + starttime = " ".join(["0"] * 15 + [str(1000 + int(pid))]) + (entry / "stat").write_text(f"{pid} (chrome) S 1 {pid} {pid} {starttime}") (entry / "cmdline").write_bytes(cmdline.replace(" ", "\0").encode()) _pid("101", f"chrome --user-data-dir={ours}session-a") @@ -261,8 +274,14 @@ def test_chrome_sweep_kills_only_this_runtimes_profile(monkeypatch, tmp_path): (proc / "self").mkdir() monkeypatch.setattr(platform_compat, "PROC_ROOT", proc) + monkeypatch.setattr(process_ownership, "PROC_ROOT", proc) killed = [] - monkeypatch.setattr(al_web.os, "kill", lambda pid, sig: killed.append(pid)) + + def _kill(pid, sig): + killed.append(pid) + shutil.rmtree(proc / str(pid), ignore_errors=True) + + monkeypatch.setattr(al_web.os, "kill", _kill) PrivateBrowserTool._terminate_owned_chrome({"TMPDIR": str(tmpdir)}) diff --git a/tests/test_workspace_artifact_tool_floor.py b/tests/test_workspace_artifact_tool_floor.py index c92bda36a..3d75b96c5 100644 --- a/tests/test_workspace_artifact_tool_floor.py +++ b/tests/test_workspace_artifact_tool_floor.py @@ -1,5 +1,7 @@ from pathlib import Path +import pytest + def test_unoffered_artifact_recovery_is_bounded(): from src.agent_loop import _artifact_unoffered_recovery_exhausted diff --git a/website/configuration-reference.md b/website/configuration-reference.md index 60a9e972c..110744fcf 100644 --- a/website/configuration-reference.md +++ b/website/configuration-reference.md @@ -90,7 +90,7 @@ The source tree reads **109** `ODYSSEUS_*` variables: 79 an operator may want to | `ODYSSEUS_BROWSER_MCP_REQUIRE_CACHE` | `''` | `src/builtin_mcp.py:90` | Truthy refuses to start the browser MCP server unless its npm package is already in the npx cache, instead of installing it at startup. | | `ODYSSEUS_BROWSER_NAMESPACE` | `'odysseus-ui'` | `src/agent_tools/web_tools.py:100` (+3 more) | Namespace for the detached agent-browser daemon's pid files, so two runtimes on one machine do not terminate each other's browsers. | | `ODYSSEUS_BROWSER_NO_SANDBOX` | `'1'` | `src/builtin_mcp.py:142` | Security-relevant. On by default, adding `--no-sandbox` because the Docker image cannot use the Chromium sandbox. Set 0, false or no to keep it. | -| `ODYSSEUS_BROWSER_SCREENSHOT_DIR` | *unset* | `src/agent_tools/web_tools.py:3458` | Where private-browser screenshots are written. Falls back to the container path, then the system temp directory. | +| `ODYSSEUS_BROWSER_SCREENSHOT_DIR` | *unset* | `src/agent_tools/web_tools.py:3479` | Where private-browser screenshots are written. Falls back to the container path, then the system temp directory. | ### Container and workspace mounts