mirror of
https://github.com/usestrix/strix.git
synced 2026-09-23 00:41:50 +00:00
fix: remove ghost sub-agents and reset blocking state on resume
Three bugs fixed: 1. Ghost sub-agents (root cause of all sub-agent issues): Restoring tracer.agents/tool_executions injected old sub-agent entries that had no live instances. The TUI showed them as interactive but they could not receive messages or run. Worse, they polluted the agent message-routing system so new sub-agents spawned after resume failed to communicate with the root agent. Fix: only restore chat_messages, vulnerability_reports, and the execution ID counter. The root agent's LLM context (message history) already knows what all sub-agents did. 2. Root agent stuck in wait state after resume: If the scan was interrupted while the root agent was in a wait state (waiting_for_input=True, stop_requested=True, etc.) the restored AgentState had those flags set and the loop froze immediately. Fix: reset all blocking flags on restore in both cli.py and tui.py. 3. Completed flag causing instant exit: If completed=True was serialised into the checkpoint (edge case) the loop would exit on the first should_stop() check. Fix: reset completed=False on restore. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
91fb78179c
commit
d1cddf2a74
2 changed files with 32 additions and 20 deletions
|
|
@ -120,12 +120,20 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915
|
|||
)
|
||||
resumed_state.max_iterations_warning_sent = False # Reset warning flag
|
||||
|
||||
# Clear sandbox so a fresh container is always created on resume
|
||||
# (the old container may be gone).
|
||||
# Clear sandbox — old container is gone, always start fresh
|
||||
resumed_state.sandbox_id = None
|
||||
resumed_state.sandbox_token = None
|
||||
resumed_state.sandbox_info = None
|
||||
|
||||
# Fix: reset any blocking flags that were set when the scan was interrupted.
|
||||
# If the scan was paused mid-wait the restored state would still have
|
||||
# waiting_for_input=True / stop_requested=True and the loop would freeze.
|
||||
resumed_state.waiting_for_input = False
|
||||
resumed_state.waiting_start_time = None
|
||||
resumed_state.stop_requested = False
|
||||
resumed_state.completed = False
|
||||
resumed_state.llm_failed = False
|
||||
|
||||
start_text = Text()
|
||||
if is_resuming:
|
||||
start_text.append("Penetration test resumed", style="bold #22c55e")
|
||||
|
|
@ -203,17 +211,18 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915
|
|||
tracer = Tracer(args.run_name)
|
||||
tracer.set_scan_config(scan_config)
|
||||
|
||||
# Added for Resume Feature — pre-populate tracer so stats/vulns are correct
|
||||
# Also restores sub-agent registry and tool executions so the full run is visible
|
||||
# Added for Resume Feature — restore conversation history and findings.
|
||||
# NOTE: We intentionally do NOT restore tracer.agents or tracer.tool_executions.
|
||||
# Injecting old sub-agent entries creates "ghost" agents: they appear in the
|
||||
# sidebar with no live instance, can't receive messages, and block real sub-agents
|
||||
# spawned in the new session from communicating with the root agent.
|
||||
# The root agent's full LLM context (in resumed_state.messages) already knows
|
||||
# what every sub-agent did — that is sufficient to continue correctly.
|
||||
if is_resuming and checkpoint_data:
|
||||
tracer.chat_messages.extend(checkpoint_data.tracer_chat_messages)
|
||||
tracer.vulnerability_reports.extend(checkpoint_data.tracer_vulnerability_reports)
|
||||
# Restore every agent (root + sub-agents) with their last-known status
|
||||
tracer.agents.update(checkpoint_data.tracer_agents)
|
||||
# Restore tool execution records; keys were serialised as str, restore as int
|
||||
for k, v in checkpoint_data.tracer_tool_executions.items():
|
||||
tracer.tool_executions[int(k)] = v
|
||||
# Advance the ID counter so new executions don't overwrite saved ones
|
||||
# Advance execution ID counter past old IDs to avoid collisions with
|
||||
# tool executions the live scan will create.
|
||||
if checkpoint_data.tracer_next_execution_id > tracer._next_execution_id:
|
||||
tracer._next_execution_id = checkpoint_data.tracer_next_execution_id
|
||||
|
||||
|
|
|
|||
|
|
@ -709,20 +709,17 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||
self.tracer.set_scan_config(self.scan_config)
|
||||
set_global_tracer(self.tracer)
|
||||
|
||||
# Added for Resume Feature — pre-populate tracer with checkpoint data so
|
||||
# stats and findings reflect the full scan history including past sessions.
|
||||
# Also restores sub-agents and their tool executions so the TUI sidebar
|
||||
# shows every agent (completed or in-progress) from the previous session.
|
||||
# Added for Resume Feature — restore conversation history and findings.
|
||||
# We do NOT restore tracer.agents or tracer.tool_executions because old
|
||||
# sub-agent entries become "ghost" agents: visible in the sidebar, impossible
|
||||
# to interact with, and they break message routing for new sub-agents spawned
|
||||
# in the resumed session. The root agent's LLM message history already
|
||||
# contains everything the sub-agents did — that is enough to resume correctly.
|
||||
_cp = getattr(args, "_checkpoint_data", None)
|
||||
if _cp and getattr(args, "resume_from_checkpoint", False):
|
||||
self.tracer.chat_messages.extend(_cp.tracer_chat_messages)
|
||||
self.tracer.vulnerability_reports.extend(_cp.tracer_vulnerability_reports)
|
||||
# Restore full agent registry (root + all sub-agents)
|
||||
self.tracer.agents.update(_cp.tracer_agents)
|
||||
# Restore tool execution records (keys were serialised as str)
|
||||
for k, v in _cp.tracer_tool_executions.items():
|
||||
self.tracer.tool_executions[int(k)] = v
|
||||
# Advance execution ID counter to avoid collisions
|
||||
# Advance execution ID counter past previous session IDs
|
||||
if _cp.tracer_next_execution_id > self.tracer._next_execution_id:
|
||||
self.tracer._next_execution_id = _cp.tracer_next_execution_id
|
||||
|
||||
|
|
@ -787,6 +784,12 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||
resumed_state.sandbox_id = None
|
||||
resumed_state.sandbox_token = None
|
||||
resumed_state.sandbox_info = None
|
||||
# Reset any blocking flags set at the moment of interruption
|
||||
resumed_state.waiting_for_input = False
|
||||
resumed_state.waiting_start_time = None
|
||||
resumed_state.stop_requested = False
|
||||
resumed_state.completed = False
|
||||
resumed_state.llm_failed = False
|
||||
config["state"] = resumed_state
|
||||
|
||||
_mgr = getattr(args, "_checkpoint_manager", None)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue