Skip to content

fix(agent): prevent plan execution from stalling after update_plan#670

Draft
gnanam1990 wants to merge 2 commits into
mainfrom
fix/issue-666-plan-stall
Draft

fix(agent): prevent plan execution from stalling after update_plan#670
gnanam1990 wants to merge 2 commits into
mainfrom
fix/issue-666-plan-stall

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes an agent-loop failure where Zero could remain indefinitely on the first active plan step after update_plan.
When a model returned continuation-cue text or reasoning-only responses without productive tool calls, interactive runs could accept the response as complete or repeatedly consume turns without reporting a visible failure.

Root cause

  • The completion gate was applied only when RequireCompletionSignal was explicitly enabled.
  • Interactive TUI runs therefore accepted some non-productive text turns while plan items remained unfinished.
  • Reasoning-only responses reset the empty-turn protection, allowing repeated non-productive turns until the turn budget was exhausted.
  • The plan panel continued displaying the last valid update_plan state, leaving the first step active.

Fix

  • Apply the completion gate whenever unfinished plan items remain.
  • Treat completed reasoning-only turns as non-productive while a plan is pending.
  • Preserve the existing bounded continuation-nudge behaviour.
  • Propagate incomplete results to the TUI.
  • Show a visible incomplete-run notice.
  • Mark the active plan step failed instead of falsely completing remaining steps.
  • Keep no-plan and ordinary completion behaviour unchanged.

Tests

Added regression coverage for:

  • pending-plan completion gating without RequireCompletionSignal,
  • bounded handling of reasoning-only turns,
  • multi-chunk reasoning within one provider turn counting as one non-productive turn,
  • reasoning-only turn followed by productive tool call succeeding,
  • non-colon mid-step text nudged while plan pending,
  • bounded nudge then valid final answer on TUI path,
  • cancellation during plan-pending run,
  • repeated identical update_plan hitting max turns,
  • TUI incomplete-state handling,
  • active-step failure without completing pending steps.

Verification

  • go fmt ./...
  • go vet ./...
  • go test ./...
  • make build
  • go run golang.org/x/vuln/cmd/govulncheck@v1.3.0 ./...

Lint comparison (base 80c39aa vs fix 6360909)

golangci-lint with --enable-only unused,ineffassign,staticcheck reports 36 issues on both base and fix branches. No new failures in changed production files (internal/agent/loop.go, internal/agent/guardrails.go, internal/tui/plan_panel.go, internal/tui/model.go). Pre-existing failures remain in unrelated packages (internal/sandbox, internal/tui helpers, internal/cli, etc.).

Smoke test

Live HY3 smoke test not performed — no HY3/OpenGateway credentials configured locally (active provider is xai). Deterministic fake-provider tests are the primary reproduction.

Fixes #666

Summary by CodeRabbit

  • Bug Fixes
    • Improved plan-aware “completion gate” and runaway/no-output behavior so stalls with pending plan steps use bounded “continue” nudges and end with correct incomplete outcomes.
    • Runs that stop mid-plan are now reported as incomplete (not successful), with remaining plan steps updated accordingly.
    • Cancellation during plan-pending stalls now stops immediately with a canceled result.
    • Prevented silent looping on repeated identical plan updates by enforcing max-turn limits.
  • Reliability / UX
    • Refined reasoning-only turn detection while a plan is pending.
    • The TUI now displays “Run incomplete” as a system notice during resume/rehydration, with appropriate transcript behavior.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 00cac720-7a76-4c3c-b327-4a90cf735fde

📥 Commits

Reviewing files that changed from the base of the PR and between e0baf80 and f009491.

📒 Files selected for processing (12)
  • internal/agent/completion_gate_test.go
  • internal/agent/guardrails.go
  • internal/agent/guardrails_test.go
  • internal/agent/loop.go
  • internal/agent/types.go
  • internal/sessions/store.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/plan_panel.go
  • internal/tui/plan_panel_progress_test.go
  • internal/tui/resume_task_test.go
  • internal/tui/session.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • internal/tui/session.go
  • internal/sessions/store.go
  • internal/tui/resume_task_test.go
  • internal/tui/plan_panel_progress_test.go
  • internal/agent/types.go
  • internal/tui/model_test.go
  • internal/agent/guardrails.go
  • internal/tui/plan_panel.go
  • internal/agent/guardrails_test.go
  • internal/tui/model.go
  • internal/agent/loop.go
  • internal/agent/completion_gate_test.go

Walkthrough

The agent now applies completion gating to pending plans, stops non-productive stalled runs as incomplete, and propagates incomplete status to the TUI and session transcripts. Tests cover nudges, cancellation, max turns, reasoning-only turns, and unfinished plan rendering.

Changes

Plan-aware run completion

Layer / File(s) Summary
Agent gates and guardrails
internal/agent/guardrails.go, internal/agent/loop.go, internal/agent/types.go, internal/agent/guardrails_test.go
Pending plan items influence completion gating and no-progress detection; stalled runs become incomplete, while productive tool calls can still finish the plan.
Completion and run-control validation
internal/agent/completion_gate_test.go
Tests cover bounded continue nudges, final-answer acceptance, cancellation during stalls, and repeated plan updates reaching MaxTurns.
TUI incomplete-state propagation
internal/sessions/store.go, internal/tui/model.go, internal/tui/plan_panel.go, internal/tui/model_test.go, internal/tui/plan_panel_progress_test.go
Incomplete responses are recorded and rendered separately; active plan steps become failed, pending steps remain pending, and incomplete runs do not trigger auto-titling.
Incomplete session transcript hydration
internal/tui/session.go, internal/tui/resume_task_test.go
Incomplete session events hydrate into system transcript rows prefixed with Run incomplete:.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Provider
  participant AgentLoop
  participant GuardState
  participant TUI
  participant SessionStore
  Provider->>AgentLoop: Emits a provider turn
  AgentLoop->>GuardState: Observes turn and pending plan items
  GuardState-->>AgentLoop: Requests continuation or stops incomplete
  AgentLoop->>TUI: Sends incomplete result and reason
  TUI->>TUI: Marks active steps failed and preserves pending steps
  TUI->>SessionStore: Records run_incomplete event
  SessionStore-->>TUI: Hydrates incomplete system transcript row
Loading

Possibly related PRs

  • Gitlawb/zero#315: Overlaps with TUI plan progress and step-status handling.
  • Gitlawb/zero#325: Overlaps with pending-plan completion gates and continuation nudges.
  • Gitlawb/zero#608: Uses the RequireCompletionSignal option affected by these completion-gate changes.

Suggested reviewers: vasanthdev2004

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: preventing plan execution from stalling after update_plan.
Linked Issues check ✅ Passed The PR addresses #666 by preventing plan stalls, completing plan steps, and surfacing incomplete runs in the TUI.
Out of Scope Changes check ✅ Passed The changes stay focused on plan-pending execution, completion gating, and incomplete-run handling with matching tests.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-666-plan-stall

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
internal/tui/model_test.go (1)

1354-1377: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Tighten the assertion to lock in "active step only" semantics.

The test only checks that some step is failed; it wouldn't catch a regression where the pending step c is also marked failed (or completed), contradicting the stated "mark the active step failed without completing pending steps" behavior.

♻️ Suggested tightened assertion
 		next := updated.(model)
 		if next.plan.isComplete() {
 			t.Fatal("incomplete turn must not force-complete the plan")
 		}
-		foundFailed := false
-		for _, step := range next.plan.steps {
-			if step.status == "failed" {
-				foundFailed = true
-			}
-		}
-		if !foundFailed {
-			t.Fatalf("expected a failed plan step, got %+v", next.plan.steps)
-		}
+		if next.plan.steps[1].status != "failed" {
+			t.Fatalf("expected the active step to be marked failed, got %+v", next.plan.steps)
+		}
+		if next.plan.steps[2].status != "pending" {
+			t.Fatalf("expected the remaining pending step untouched, got %+v", next.plan.steps)
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model_test.go` around lines 1354 - 1377, Strengthen the
assertions in the “incomplete turn marks the active plan step failed” test to
verify only the active step is failed. Confirm the active step’s status is
failed, while pending step c remains pending and is not completed or failed;
retain the existing assertion that the plan is not complete.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/model.go`:
- Around line 5178-5200: Update the incomplete-run handling in the
result-processing flow to persist a distinct replay event instead of
sessions.EventError. Use the existing dedicated incomplete-run event type or add
the appropriate separate tag, while preserving the current message payload and
transcript notice so resume replay represents the run as incomplete rather than
failed.

In `@internal/tui/plan_panel.go`:
- Around line 169-192: Update markIncompleteRemaining to set
planPanelState.completedAt to now when, after marking active steps failed, no
pending steps remain and the plan is complete; preserve it unset when pending
work remains. Extend TestPlanMarkIncompleteRemaining with coverage for the
no-pending terminal path.

---

Nitpick comments:
In `@internal/tui/model_test.go`:
- Around line 1354-1377: Strengthen the assertions in the “incomplete turn marks
the active plan step failed” test to verify only the active step is failed.
Confirm the active step’s status is failed, while pending step c remains pending
and is not completed or failed; retain the existing assertion that the plan is
not complete.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 88b11d69-d0a6-45dd-bf2d-789ebd5fc856

📥 Commits

Reviewing files that changed from the base of the PR and between 80c39aa and 6360909.

📒 Files selected for processing (9)
  • internal/agent/completion_gate_test.go
  • internal/agent/guardrails.go
  • internal/agent/guardrails_test.go
  • internal/agent/loop.go
  • internal/agent/types.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/plan_panel.go
  • internal/tui/plan_panel_progress_test.go

Comment thread internal/tui/model.go
Comment thread internal/tui/plan_panel.go

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Preserve pending-plan incompleteness at the max-turns exit
    internal/agent/loop.go:739
    The new plan-aware gate applies to interactive runs even when RequireCompletionSignal is false, but both max-turns exits still set Result.Incomplete only when that option is true. A TUI run can therefore repeatedly make tool calls (including update_plan with an in-progress item) until MaxTurns, receive the forced final summary, and be treated as successful; model.updateModel then calls completeRemaining and marks every untouched step complete. This recreates the false-completion state the PR is fixing. Mark a max-turn cutoff incomplete when pending plan items remain as well, and assert the result/TUI state in the repeated-plan regression.

  • [P2] Keep the incomplete-run event in resumed prompt context
    internal/sessions/store.go:40
    EventRunIncomplete is rendered on TUI resume, but the session prompt filter in exec_session.go does not retain this new event type. Consequently, the next TUI or exec --resume agent invocation receives the final assistant message but not the reason that its prior run stopped unfinished, so it can reason from an apparent successful completion. Include the new event in promptContextEvents and cover resumed/forked prompt construction.

  • [P2] Persist the final answer before its incomplete notice
    internal/tui/model.go:5184
    Live rendering appends the assistant's final answer and then the Run incomplete notice, but persistence appends EventRunIncomplete before EventMessage. Session hydration replays events in stored order, so reopening the session reverses the visible chronology and presents the warning before the answer it qualifies. Store the assistant message first (or otherwise preserve the live ordering) and add an ordered replay test.

KRATOS added 2 commits July 15, 2026 12:53
Apply the plan-aware completion gate to interactive runs while update_plan
still has pending items, count reasoning-only turns as non-productive during
an active plan, surface Incomplete results in the TUI, and mark the active
plan step failed instead of force-completing a stalled run.
Use EventRunIncomplete instead of EventError for stalled plan runs so
resume replays a system notice rather than a hard error. Stamp
completedAt when markIncompleteRemaining leaves a terminal plan, tighten
incomplete plan assertions, and add hydration coverage.
@gnanam1990 gnanam1990 force-pushed the fix/issue-666-plan-stall branch from e0baf80 to f009491 Compare July 15, 2026 07:23

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Preserve pending-plan incompleteness at the max-turns exit
    internal/agent/loop.go:739
    The new plan-aware gate applies to interactive/TUI runs even when RequireCompletionSignal is false, but both max-turns exits still set Result.Incomplete only when that option is true. A TUI run can keep making tool calls (including update_plan with an in-progress item) until MaxTurns, receive the forced final summary, and be treated as successful; model.updateModel then calls completeRemaining and marks every untouched step complete. This recreates the false-completion state this PR is intended to prevent. Mark a max-turn cutoff incomplete when pending plan items remain as well, and assert the result/TUI state in the repeated-plan regression.

  • [P1] Derive the completion gate from a successfully applied plan
    internal/agent/guardrails.go:541
    observeTurn updates planItemsPending from raw update_plan arguments before the tool is executed. For example, with an existing pending TUI plan, a call such as {"plan":[{"status":"completed"}]} clears the guard count, but update_plan rejects the missing content and leaves the actual plan unchanged. The next text turn bypasses the new interactive completion gate and completeRemaining falsely completes that still-pending panel. This also diverges from the dispatcher’s concatenated-JSON recovery path. Only replace guard plan state after a successful, normalized update (or retain the prior state on a failed call), and cover the failed-update regression.

  • [P2] Keep the incomplete-run event in resumed prompt context
    internal/sessions/exec_session.go:194
    EventRunIncomplete is rendered on TUI resume, but the session prompt filter does not retain the new event type. Consequently, the next TUI or exec --resume invocation receives the final assistant message but not the reason that its prior run stopped unfinished, so it can reason from an apparent successful completion. Include the event in promptContextEvents and cover resumed/forked prompt construction.

  • [P2] Persist the final answer before its incomplete notice
    internal/tui/model.go:5184
    Live rendering appends the assistant's final answer and then the Run incomplete notice, but persistence appends EventRunIncomplete before EventMessage. Session hydration replays events in stored order, so reopening the session reverses the visible chronology and presents the warning before the answer it qualifies. Store the assistant message first (or otherwise preserve the live ordering) and add an ordered replay test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug Zero getting stuck on a plan

2 participants