[eric] tests: the suite can never resume real chats with live credentials (ENG-388)

This commit is contained in:
ciregenz
2026-08-21 11:51:14 -07:00
parent 6cc705c0ee
commit df31e8d5d2
3 changed files with 70 additions and 0 deletions
@@ -4,6 +4,7 @@ on boot). Split from SessionLifecycle (which handles ONE session at a time) so e
one concern. self.sessions resolves across the MRO as before."""
import logging
import sys
from typeguard import typechecked
@@ -18,6 +19,13 @@ from backend.apps.agents.manager.session.apply_context_window import apply_conte
logger = logging.getLogger(__name__)
def running_under_test() -> bool:
"""Auto-resume dispatches REAL turns: live credentials, live Bash, in whatever tree the process
was started from. A test that boots the app lifespan must never do that to the developer's own
chats, so this is the one gate that keeps a suite run from becoming an agent run."""
return "pytest" in sys.modules
from backend.apps.agents.manager.AgentManagerProtocol import AgentManagerProtocol
@@ -80,6 +88,10 @@ class SessionPersistence(AgentManagerProtocol):
"""Fire one hidden continuation into each crash-interrupted session (called after
restore, off the boot critical path). Failure is per-session and non-fatal: a session
that cannot resume just keeps its amber chip."""
if running_under_test():
logger.info("crash-resume: skipped, running under test")
self.crash_resume_queue = []
return
for sid in list(getattr(self, "crash_resume_queue", []) or []):
try:
# send_message lives on the Messaging mixin; AgentManager composes both.
+9
View File
@@ -58,6 +58,13 @@ def test_second_consecutive_crash_trips_the_breaker(manager, tmp_path, monkeypat
assert json.load(f)["crash_interrupt_count"] == 2
def p_act_like_a_real_app(monkeypatch) -> None:
"""Auto-resume refuses to dispatch under pytest, because it sends REAL turns and the suite shares
the developer's data root (ENG-388). These tests are about the production path, so they opt out."""
import backend.apps.agents.manager.session.SessionPersistence as p_sp
monkeypatch.setattr(p_sp, "running_under_test", lambda: False)
def test_waiting_approval_never_auto_resumes(manager, tmp_path, monkeypatch):
p_write_session(tmp_path, monkeypatch, "s-appr", "waiting_approval", [p_msg("user"), p_msg("tool_call")])
asyncio.run(manager.reconcile_on_startup())
@@ -65,6 +72,7 @@ def test_waiting_approval_never_auto_resumes(manager, tmp_path, monkeypatch):
def test_auto_resume_sends_one_hidden_continuation(manager, tmp_path, monkeypatch):
p_act_like_a_real_app(monkeypatch)
p_write_session(tmp_path, monkeypatch, "s-cut", "running", [p_msg("user"), p_msg("tool_call")])
asyncio.run(manager.reconcile_on_startup())
sent = []
@@ -81,6 +89,7 @@ def test_auto_resume_sends_one_hidden_continuation(manager, tmp_path, monkeypatc
def test_resume_failure_is_per_session_and_non_fatal(manager, tmp_path, monkeypatch):
p_act_like_a_real_app(monkeypatch)
p_write_session(tmp_path, monkeypatch, "s-a", "running", [p_msg("user"), p_msg("tool_call")])
p_write_session(tmp_path, monkeypatch, "s-b", "running", [p_msg("user"), p_msg("tool_call")])
asyncio.run(manager.reconcile_on_startup())
@@ -0,0 +1,49 @@
"""A test run must never become an agent run.
The bug class (caught live 2026-08-21): `agents_lifespan` fires `auto_resume_crashed_turns()`, which
sends a real hidden continuation into every crash-interrupted session. The suite does not isolate the
data root, so any test that boots the app lifespan resumed the DEVELOPER'S OWN chats with live
credentials and live Bash, inside whatever tree the suite was running from. Observed: a full
`pytest backend/tests` run spawned two real CLI processes which each started their own
`pytest backend/tests/` run, because that was the task those chats had been interrupted mid-way
through. It spends real money, rewrites real session files, and executes commands from old
conversations against the working tree.
The seal: auto-resume refuses to dispatch when pytest is loaded.
"""
import asyncio
import backend.apps.agents.manager.session.SessionPersistence as p_sp
from backend.apps.agents.agent_manager import agent_manager
def p_capture_sends(monkeypatch) -> list:
sent: list = []
async def fake_send(session_id, prompt, hidden=False, **kwargs):
sent.append(session_id)
monkeypatch.setattr(agent_manager, "send_message", fake_send)
agent_manager.crash_resume_queue = ["sid-a", "sid-b"]
return sent
def test_auto_resume_never_dispatches_under_test(monkeypatch):
sent = p_capture_sends(monkeypatch)
asyncio.run(agent_manager.auto_resume_crashed_turns())
assert sent == [], "a suite run must not spend credentials resuming real chats"
assert agent_manager.crash_resume_queue == [], "the queue is still cleared, so nothing resumes later either"
def test_the_gate_is_what_stops_it(monkeypatch):
"""Control: with the gate reporting a normal app process, the very same call does dispatch.
Without this arm the test above passes even if auto-resume quietly stopped working."""
sent = p_capture_sends(monkeypatch)
monkeypatch.setattr(p_sp, "running_under_test", lambda: False)
asyncio.run(agent_manager.auto_resume_crashed_turns())
assert sent == ["sid-a", "sid-b"]
def test_the_gate_sees_pytest():
assert p_sp.running_under_test() is True