fix(run_agent): gate concurrent checkpoint preflight on block_result (fixes #34827)
In the concurrent tool-execution path, checkpoint preflight (write_file, patch, destructive terminal) fired BEFORE plugin guardrail block_result was computed. A blocked write_file could still dirty checkpoint state (doc_modified_this_turn, _last_write_file_call_id, turn_counter). Move checkpoint preflight to AFTER block_result computation, gated on `if block_result is None:` — matching the invariant the sequential path already enforces.
This commit is contained in:
+27
-22
@@ -180,28 +180,9 @@ def execute_tool_calls_concurrent(agent, assistant_message, messages: list, effe
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Checkpoint for file-mutating tools
|
||||
if function_name in {"write_file", "patch"} and agent._checkpoint_mgr.enabled:
|
||||
try:
|
||||
file_path = function_args.get("path", "")
|
||||
if file_path:
|
||||
work_dir = agent._checkpoint_mgr.get_working_dir_for_path(file_path)
|
||||
agent._checkpoint_mgr.ensure_checkpoint(work_dir, f"before {function_name}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Checkpoint before destructive terminal commands
|
||||
if function_name == "terminal" and agent._checkpoint_mgr.enabled:
|
||||
try:
|
||||
cmd = function_args.get("command", "")
|
||||
if _is_destructive_command(cmd):
|
||||
cwd = function_args.get("workdir") or os.getenv("TERMINAL_CWD", os.getcwd())
|
||||
agent._checkpoint_mgr.ensure_checkpoint(
|
||||
cwd, f"before terminal: {cmd[:60]}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ── Block evaluation (BEFORE checkpoint preflight) ───────────
|
||||
# We must know whether the tool will execute before touching
|
||||
# checkpoint state (dedup slot, real snapshots).
|
||||
block_result = None
|
||||
blocked_by_guardrail = False
|
||||
if _ts_scope_block is not None:
|
||||
@@ -224,6 +205,30 @@ def execute_tool_calls_concurrent(agent, assistant_message, messages: list, effe
|
||||
block_result = agent._guardrail_block_result(guardrail_decision)
|
||||
blocked_by_guardrail = True
|
||||
|
||||
# ── Checkpoint preflight (only for tools that will execute) ──
|
||||
if block_result is None:
|
||||
# Checkpoint for file-mutating tools
|
||||
if function_name in {"write_file", "patch"} and agent._checkpoint_mgr.enabled:
|
||||
try:
|
||||
file_path = function_args.get("path", "")
|
||||
if file_path:
|
||||
work_dir = agent._checkpoint_mgr.get_working_dir_for_path(file_path)
|
||||
agent._checkpoint_mgr.ensure_checkpoint(work_dir, f"before {function_name}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Checkpoint before destructive terminal commands
|
||||
if function_name == "terminal" and agent._checkpoint_mgr.enabled:
|
||||
try:
|
||||
cmd = function_args.get("command", "")
|
||||
if _is_destructive_command(cmd):
|
||||
cwd = function_args.get("workdir") or os.getenv("TERMINAL_CWD", os.getcwd())
|
||||
agent._checkpoint_mgr.ensure_checkpoint(
|
||||
cwd, f"before terminal: {cmd[:60]}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
parsed_calls.append((tool_call, function_name, function_args, block_result, blocked_by_guardrail))
|
||||
|
||||
# ── Logging / callbacks ──────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user