Compare commits

...

1 Commits

Author SHA1 Message Date
hamza.khyari 4e2e8b7ceb fix(plan): preserve strategy_decisions_json in error_details during execute and report actual actor mode
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 1m9s
CI / quality (pull_request) Successful in 1m17s
CI / typecheck (pull_request) Successful in 1m21s
CI / build (pull_request) Successful in 37s
CI / security (pull_request) Successful in 1m39s
CI / helm (pull_request) Successful in 27s
CI / integration_tests (pull_request) Successful in 3m36s
CI / e2e_tests (pull_request) Successful in 3m47s
CI / unit_tests (pull_request) Successful in 5m3s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 22s
CI / status-check (pull_request) Failing after 3s
Fixed three bugs in _run_execute_with_actor (formerly _run_execute_with_stub):

1. Merged error_details instead of replacing - preserves
   strategy_decisions_json stored by run_strategize() so retry plans
   retain the full decision hierarchy (Forgejo #10874).

2. Used type(self._execute_actor).__name__ instead of hardcoded "stub"
   at all three locations (success, on_error checkpoint, fail) so the
   mode field reflects the actual actor type (Forgejo #10874).

3. Renamed method from _run_execute_with_stub to
   _run_execute_with_actor since it accepts any configured actor
   (stub or LLM), not just ExecuteStubActor.

Updated test step comments and docs.

ISSUES CLOSED: #10874
2026-04-29 14:08:46 +00:00
4 changed files with 19 additions and 14 deletions
+1 -1
View File
@@ -72,7 +72,7 @@ raise ReadOnlyViolationError(
Read-only tool specs pass through unchanged.
`ExecuteStubActor.execute()` propagates the plan's `read_only` flag
into `ChangeSetCapture`, and `PlanExecutor._run_execute_with_stub()`
into `ChangeSetCapture`, and `PlanExecutor._run_execute_with_actor()`
reads `plan.read_only` from the plan model.
### 4. CLI Fail-Fast Guards
+1 -1
View File
@@ -330,7 +330,7 @@ Feature: PlanExecutor comprehensive coverage
And the cov2 lifecycle should have called fail_execute
# ------------------------------------------------------------------
# PlanExecutor._run_execute_with_stub - error recovery / retry
# PlanExecutor._run_execute_with_actor - error recovery / retry
# ------------------------------------------------------------------
Scenario: run_execute stub calls fail_execute on exception without recovery
@@ -903,7 +903,7 @@ def step_cov2_check_fail_execute(context: Context) -> None:
# ----------------------------------------------------------------------
# PlanExecutor._run_execute_with_stub - retry logic
# PlanExecutor._run_execute_with_actor - retry logic
# ----------------------------------------------------------------------
@@ -821,7 +821,7 @@ class PlanExecutor:
raise ValidationError("plan_id must not be empty")
if self._execution_context is not None:
return self._run_execute_with_runtime(plan_id, stream_callback)
return self._run_execute_with_stub(plan_id, stream_callback)
return self._run_execute_with_actor(plan_id, stream_callback)
def _build_decisions(self, plan: Any) -> list[StrategyDecision]:
"""Build decisions for the Execute phase.
@@ -1005,12 +1005,12 @@ class PlanExecutor:
self._lifecycle.fail_execute(plan_id, error_msg)
raise
def _run_execute_with_stub(
def _run_execute_with_actor(
self,
plan_id: str,
stream_callback: StreamCallback | None = None,
) -> ExecuteResult:
"""Execute using the legacy ExecuteStubActor with optional retry."""
"""Execute using the configured execute actor with optional retry."""
plan = self._guard_execute(plan_id)
decisions = self._build_decisions(plan)
@@ -1043,11 +1043,13 @@ class PlanExecutor:
plan = self._lifecycle.get_plan(plan_id)
plan.changeset_id = result.changeset_id
plan.sandbox_refs = result.sandbox_refs
plan.error_details = {
existing = dict(plan.error_details or {})
existing.update({
"tool_calls_count": str(result.tool_calls_count),
"sandbox_refs_count": str(len(result.sandbox_refs)),
"mode": "stub",
}
"mode": type(self._execute_actor).__name__,
})
plan.error_details = existing
plan.timestamps.updated_at = datetime.now(tz=UTC)
# Spawn and execute child subplans from spawn decisions
@@ -1067,10 +1069,11 @@ class PlanExecutor:
OperationalMetricKey.PLAN_DURATION_MS, plan_id, _duration_ms
)
self._logger.info(
"Execute completed (stub)",
"Execute completed",
plan_id=plan_id,
changeset_id=result.changeset_id,
tool_calls=result.tool_calls_count,
actor=type(self._execute_actor).__name__,
)
return result
@@ -1110,17 +1113,19 @@ class PlanExecutor:
"on_error",
{
"exception_type": type(last_exc).__name__,
"mode": "stub",
"mode": type(self._execute_actor).__name__,
},
)
self._try_rollback_to_last_checkpoint(plan_id)
error_msg = f"{type(last_exc).__name__}: {last_exc}"
plan = self._lifecycle.get_plan(plan_id)
plan.error_details = {
existing = dict(plan.error_details or {})
existing.update({
"exception_type": type(last_exc).__name__,
"traceback": traceback.format_exc(),
"mode": "stub",
}
"mode": type(self._execute_actor).__name__,
})
plan.error_details = existing
self._lifecycle._commit_plan(plan)
self._lifecycle.fail_execute(plan_id, error_msg)
raise last_exc