Skip to content

fix(agent-status): show a failed or stopped OpenCode turn, and the screens #22944 missed - #23105

Closed
brennanb2025 wants to merge 18 commits into
mainfrom
brennanb2025/agent-status-failed-turn-ending
Closed

brennanb2025 wants to merge 18 commits into
mainfrom
brennanb2025/agent-status-failed-turn-ending

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

When an OpenCode turn ends because of an error (the model service rejected the request, the context overflowed) or because you pressed Esc, Orca showed it as a turn that finished well: a green Done. Claude and Grok already show Failed since #22944; this PR brings OpenCode (1.x and 2.x) up to the same place, and fixes the last few screens that still showed a failed turn as Done or Working: the pop-out agent board, the "send notes to agent" picker, and the orchestration worker list, where a failed worker also never raised attention to its coordinator.

What Changed

This PR fixes a failed or user-stopped agent turn reading as a clean finish in the places #22944 did not reach. It builds on #22944 (main already records the verdict as mainAgent.outcome and draws Failed / Interrupted on the sidebar, tabs, worktree status, activity views, notifications, auto-sleep, Smart Sort, worktree ps rows and phone agent rows); it adds no second verdict and no new display rules.

Scope. Changes: OpenCode 1.x and 2.x status (local and over SSH, since Orca ships the same plugin to the relay), the pop-out agent board, the review-notes agent picker, the dashboard row's fallback label, orca orchestration worker-list, and orchestration fleet attention. Not affected: Claude, Grok and Codex producers; the combined lifecycle state that waits, automations and sleeping read; every surface #22944 already covers.

Before → after, as the user sees it:

  • OpenCode 1.x: a turn that ended in a provider error read Done → now Failed; a turn stopped with Esc read Done → now Interrupted. Everywhere fix(native-chat): a request that failed reads as failed #22944 already shows Failed now shows it for OpenCode too (row, card, tab, worktree status, "OpenCode failed" notification, no star prompt, not auto-slept).
  • OpenCode 2.x: same, whether it runs as opencode or from Orca's own OpenCode 2 launch entry. A failed or stopped OpenCode 2 turn read Done → now Failed / Interrupted. A context overflow whose compaction succeeds still reads Done.
  • Ctrl+C on a Grok or OpenCode turn (local or over SSH) or an SSH Claude turn, followed by the aborted request reporting its error: the row flipped from Interrupted to Failed → now stays Interrupted. A failure in a new turn after the stop still shows Failed.
  • Pop-out agent board: a failed agent sat under Done with a green dot (or under Working while its subagents ran), and a stopped one read green Done → now a Failed or Interrupted dot, drawn by the same status dot the sidebar uses (a stop's colour follows that shared dot). The card sits under Done until you have seen it, then settles into Idle like a seen completion and keeps its mark; a failed agent whose subagents still run stays under Done, and one whose subagent is asking a question stays under Needs You with that question shown and reads Failed once it is answered, as the sidebar card ranks it. The per-column counts on the sidebar's Agent Dashboard entry follow the same columns.
  • "Send notes to agent" picker: a failed agent was labelled Done or Working → now Failed. A dashboard row with no captured prompt labelled a failed agent from the combined state → now Failed.
  • Orchestration: a worker whose turn failed and never reported done showed activity=done, attention=none → fleet attention now raises one failure, and worker-list prints failed (or interrupted) in the stage slot.

Mechanism.

  • OpenCode's plugin now sends the root session's own state (root_state) and the name of the error that ended its turn (root_turn_error_name) beside the pane fold it already sends; the listener turns them into mainAgent (MessageAbortedError = cancellation, any other name = failure). A plugin that predates the fields publishes no mainAgent, so nothing is inferred from the fold. OpenCode 2 has no session.error, so its setup bridge restates the run-ending session.execution.failed (by its error type) and a user's session.execution.interrupted as one before the idle.
  • The store's cancel hold (server-cancel-verdict-latch.ts) no longer releases on a settled mainAgent whose outcome is failure: that failure ends the same turn the user already stopped.
  • Dashboard cards carry the verdict as an optional verdictMark taken from main's agentVerdictDisplayMark; the picker and the fallback label read the same function.
  • The fleet evidence keeps the row's mainAgent, the worker's stage carries it (optional), and attention re-derives failure from it on every read by the display rule (agentVerdictDisplayMark draws the main agent as Failed), so a turn the rows show as Failed always raises it and the next turn clears it.

Why

  • No new verdict, no new names. fix(native-chat): a request that failed reads as failed #22944 already models how a turn ended as mainAgent.outcome and names the display rule (agentVerdictDisplayMark). Each change here is either a producer that never wrote that fact (OpenCode) or a reader that still read the combined state; extending them removes the gap without a parallel module that could disagree.
  • Precedence stays main's. A failed main agent outranks every combined state on its own row, and a failure ranks like a completion for attention, so the pop-out board files it under Done rather than beside questions (a subagent's own question still files the card under Needs You), and Smart Sort, the tab bell and staleness keep fix(native-chat): a request that failed reads as failed #22944's rules. Subagents keep their own rows, so a subagent's question stays visible on the subagent.
  • Derived, not stored. Every new reader derives the mark from mainAgent; fleet attention re-derives failure on each read instead of latching it, so it clears itself when the next turn starts.
  • OpenCode's verdict comes from the root session, not the fold. The plugin's pane state folds child sessions in, so a root that failed while a child runs would otherwise read Working; sending the root's own state beside the fold is what lets the Failed mark show at once.
  • Mixed versions are safe. Every addition is an optional field: an old plugin, host or client sends or reads none and behaves exactly as before.

Out of scope:

Linked Issue

Linear: STA-7949. Builds on #22944.

Visual Proof

Isolated background Orca dev instances on a separate Mac (hidden windows, a throwaway home folder, real agent CLIs pointed at a local mock model server). Before = main at this PR's base; after = this branch. Same scenario and moment on both sides.

OpenCode 1.18.33, the model request is rejected (400). Before: the tab and worktree card show a green Done check. After: both show Failed (red), with the "Failed" tooltip, and the notification says "OpenCode failed".

Before After
before after

OpenCode 1.18.33, the user presses Esc mid-reply. Before: green Done. After: Interrupted (the tooltip says "Interrupted"; its dot colour is the shared status dot, which #23467 makes muted).

Before After
before after

OpenCode 2.0.14 opened from Orca's OpenCode 2 entry, request rejected (400). Before: green Done. After: Failed on the row, tab and worktree card.

Before After
before after

OpenCode 2.0.14 from the OpenCode 2 entry, Esc mid-reply. Before: green Done. After: Interrupted.

Before After
before after

Pop-out agent board, a failed OpenCode turn the user has not looked at yet. Before: a green Done card. After: the card sits under Done with a Failed dot.

Before After
before after

Pop-out agent board after the user has seen a failed Claude turn. Before: the card settles into Idle and loses its mark (grey dot). After: it settles into Idle like a seen completion but keeps its Failed dot.

Before After
before after

"Send notes to agent" picker with a failed OpenCode 2 agent. Before: labelled Done. After: labelled Failed.

Before After
before after

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Live (isolated background Orca dev instances on a separate Mac, real CLIs against a local mock model server; before = main at this PR's base, after = this branch):

Scenario Before After
OpenCode 1.18.33, request rejected (400) Done, "OpenCode finished", star prompt Failed on row, tab and worktree card, "OpenCode failed"
OpenCode 1.18.33, Esc mid-reply Done Interrupted, "OpenCode stopped"
OpenCode 1.18.33, clean turn after the failure Done Done (the failure does not stick)
OpenCode 2.0.14 run as opencode, 400 / Esc Done / Done Failed / Interrupted
OpenCode 2.0.14 from Orca's OpenCode 2 entry, 400 / Esc / clean turn Done / Done / Done Failed / Interrupted / Done
Pop-out board, failed turn unseen then seen green Done, then grey Idle Failed dot under Done, then Idle keeping the Failed dot
Pop-out board, seen failed Claude turn grey Idle (mark lost) Idle with the Failed dot
"Send notes to agent" picker Done Failed
Claude request rejected (400), regression check Failed, "Claude failed" identical

Automated: every new behaviour has a test through its real boundary: the hook server (local and through the SSH relay) for the cancel hold, the generated plugin for OpenCode 1.x, a replay of event sequences captured from OpenCode 2.0.14 through the plugin's setup bridge for OpenCode 2, the fleet projection and worker attention context, the worker-list command handler, the dashboard snapshot builder / pop-out patch / payload validator / kanban card, the notes picker and dashboard row renders, and one table-driven test that runs each single-agent scenario through the pop-out card and through main's own desktop row dot and worktree-card ladder (applyAgentPaneActivityFlags / resolveWorktreeStatus) and checks they agree. Each was checked by deleting its production line, confirming the named test fails, and restoring it.

Not tested live: OpenCode over SSH (the relay ships the same plugin; unit-tested through the relay), Windows and Linux, the orchestration worker list and fleet attention (unit-tested), and a context overflow whose compaction succeeds on OpenCode 2 (the mock's compaction reply is rejected by 2.0.14, so only the failed-compaction path ran live). Notification titles on the OpenCode 2 entry read "opencode2 failed" because main already labels that route by its agent id ("opencode2 finished" before this change).

AI Disclosure

Author: @BrennanKB5

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Backwards compatibility: no new status state. New optional fields only: OpenCode plugin root_state / root_turn_error_name (host-generated plugin; an old plugin sends none and gets no mainAgent), stage.mainAgent on orchestration worker rows (an old host sends none; an old CLI ignores it), and verdictMark on pop-out dashboard cards (same app build on both ends). failure is an existing fleet attention category.
  • Remote SSH: Orca ships the same OpenCode plugin source to the relay, and the relay's listener is the same shared code, so SSH OpenCode panes publish mainAgent the same way; a relay still running an older build ignores the new fields and publishes none, as today. The cancel hold's new rule is tested through the relay for Claude, and locally for Grok and OpenCode (the rule lives in the desktop store, so the lane does not change it).
  • Until fix(agent-status): a turn a crash cut off reads Interrupted, an unproven end Couldn't confirm #23467 lands, a user's stop on the pop-out board uses the shared status dot, which main still draws red; fix(agent-status): a turn a crash cut off reads Interrupted, an unproven end Couldn't confirm #23467 makes it muted everywhere.
  • Performance: one pure derivation per row or card read; no new polling or IPC.
  • Security and cross-platform: no path, shell or platform-specific code added.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — one narrow, pre-existing edge noted below.

Reviewed changes

This run reviewed all 13 commits (135 files) of brennanb2025/agent-status-failed-turn-ending against its stack base brennanb2025/codex-cancel-with-subagent.

  • One shared display derivation — new src/shared/agent-status-display-state.ts derives waiting > failed > working > interrupted > done > decay from mainAgent plus the child-work residual; every desktop surface, the CLI, mobile, the pet and fleet attention now read it, and a failure survives reader staleness.
  • OpenCode turn endings — the status plugin publishes root_state / root_turn_error_name and the listener maps them to a mainAgent verdict (MessageAbortedError → cancellation, any other name → failure). opencode2/mimo-code deliberately publish no mainAgent, asserted by test.
  • Cancel beats a same-turn failure — resolveCancelVerdictLatch keeps a latched cancellation when a provider failure for the same turn arrives.
  • Reader plumbing — mainAgent rides RuntimeWorktreeAgentRow, fleet stage evidence and dashboard turnEnding; notifications switch from agentInterrupted to agentTurnEnding; hibernation, the star prompt, auto-sleep and fleet attention now require a clean completion.

Verification. 161 tests across the new display-state, producer-parity, OpenCode-normalization, cancel-latch, sidebar and Cmd+J suites pass locally. Three independent specialist passes (OpenCode root-state delivery, cancel-latch vs every reader, display precedence across all surfaces) each returned no defect, and the shared precedence is applied consistently.

ℹ️ A same-turn failure can still outrank a cancel when the relay's prompt cache is lost

The new rule ("a provider failure after a user cancel keeps the cancellation") sits behind the pre-existing restatesAnotherPrompt guard, which returns early before the new failure branch. A main-agent failure whose resolved prompt is empty is treated as a changed prompt and applied, so the row reads Failed. This is narrow — it needs a relay restart between the prompt and the failure — and pre-existing, but it does contradict the PR's stated guarantee.

Technical details
# Cancel latch bypassed by an empty prompt after a relay restart

## Affected sites
- `src/main/agent-hooks/server/server-cancel-verdict-latch.ts:111-125` — `restatesAnotherPrompt` is evaluated before the new `incoming.payload.mainAgent?.state === 'done'` branch, so an empty incoming prompt short-circuits to `apply`.
- `src/shared/agent-hook-listener/prompt-fields.ts:114` — `resolvePrompt` returns `''` when the per-pane cache is empty (e.g. a restarted relay).

## Required outcome
- A same-turn main-agent failure should keep the latched cancellation whether or not the relay can restate the prompt.

## Concrete sequence
1. Relayed Claude: `UserPromptSubmit 'long task'` → stored prompt `'long task'`, `working`.
2. Ctrl+C → `inferInterrupt` writes `mainAgent { done, cancellation }`; latch armed.
3. The relay restarts and loses `lastPromptByPaneKey`.
4. The aborted request fails → relay posts `StopFailure`; `resolvePrompt` returns `''`.
5. `restatesAnotherPrompt` sees `'' !== 'long task'` → `apply`; row becomes `mainAgent { done, failure }` → Failed.

## Open questions for the human
- Is this reachable in the relayed/SSH lanes you care about, and would you rather reorder the guard or exempt main-agent events with an unknown prompt as a follow-up?

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 force-pushed the brennanb2025/codex-cancel-with-subagent branch from 55f434e to 19c3015 Compare September 28, 2026 00:51
@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-status-failed-turn-ending branch from a04af01 to 12ccbfb Compare September 28, 2026 20:52
@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds failed and stopped turn display to agent status views.

The PR appears safe to merge; no outstanding findings remain.

Findings

  1. P1 Active child work gets cleared ▶

Summary

The PR carries OpenCode root-turn outcomes into agent status and updates the dashboard, notes picker, worker list, and orchestration attention to display failed or interrupted turns.

  • Adds coverage for OpenCode 1.x and 2.x turn endings and cancellation precedence.
  • Preserves the main-agent verdict alongside combined child activity.

Reviews (12) · Last reviewed commit: "test(agent-status): run the cancel-then-..."

export function isClearableActivityThread(thread: AgentPaneThread): boolean {
const id = activityThreadStatusId(thread)
return id === 'done' || id === 'interrupted'
return id === 'done' || id === 'interrupted' || id === 'failed'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Active child work gets cleared

If the main agent fails while a background child is still working, the pane remains active but its Activity thread is classified as failed. This check then lets “Clear completed” clear that thread, stamping an activity cutoff and potentially dismissing its retained snapshot before the child finishes. Only treat the thread as clearable after the child work settles.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run re-reviewed the delta since the prior pullfrog review: the new commit 3e383f77ea (plus the restack previously reviewed at 12ccbfba) against stack base brennanb2025/codex-cancel-with-subagent.

  • Shared wire resolver — extracted the unparsed-row guard (wireAgentDisplayState → resolveWireAgentPaneDisplayState) out of src/cli/agent-display-state.ts into src/shared/agent-status-display-state.ts, so the CLI and the phone read a row published by another build through one function.
  • Phone reads the shared resolver — agentDotState now calls resolveWireAgentPaneDisplayState, so a mainAgent.state arm this build does not know is dropped and falls back to the row's own state instead of falling through the precedence to done; AgentDotState is now derived from the shared AgentStatusDisplayState vocabulary.
  • Regression test — added the unknown-main-agent-arm case for phone rows, asserting working and interrupted survive rather than reading as done.

Verification. 19 mobile tests (agent-row-display, workspace-list-ordering) and 41 root tests (agent-status-display-state, worker-list-display-state, workspace-format) pass; pnpm --dir mobile run typecheck is clean. No import cycle — agent-status-types does not depend on the display module.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-status-failed-turn-ending branch from 3e383f7 to 85d6e43 Compare September 29, 2026 05:23
@brennanb2025 brennanb2025 changed the title fix(agent-status): show a turn that ended in an error as Failed, not Done fix(agent-status): show a failed or stopped OpenCode turn, and the screens #22944 missed Sep 29, 2026
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/codex-cancel-with-subagent to main September 29, 2026 05:23
@greptile-apps

greptile-apps Bot commented Sep 29, 2026

Copy link
Copy Markdown

Comments Outside Diff

These findings could not be posted inline.

  • P2 Installable Android update overlooked mobile/src/app-update/github-release-update-source.ts:53 ▶

    If three newer Android tags have no published APK-for example, after three failed builds-the checker probes only those tags and reports “current.” An older tag could still have an installable release newer than the app, but users would not be offered it.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

OpenCode hook data now reports root-turn state and error names, including failure and user-interruption outcomes for OpenCode 2. The listener normalizes this data into main-agent statuses, and cancellation handling preserves inferred cancellations when same-turn provider failures arrive. Main-agent verdicts now feed orchestration attention and appear in dashboard cards, agent rows, editor menus, and worker-list output.

Priority: ⬇️ Low

Merge Risk: 🟡 Moderate · up to 2cd08

Failed or user-stopped OpenCode 2 turns will not show the intended verdict. Include OpenCode 2 in verdict resolution before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 2cd08

The change affects whether failed work is shown and brought to a coordinator’s attention. One supported OpenCode version still appears to miss the new failure verdict, although no new external access or privilege escalation was identified.

Retained concerns

  • Medium · reliability · inferred: OpenCode 2 now produces root-turn error information, but the shared listener excludes its source from main-agent verdict resolution. A failed OpenCode 2 worker consequently cannot raise the new verdict-based attention signal through this path. The absence of its verdict predates the PR; the concern is that the new cross-component contract remains incomplete.
Security review details

Security Blast Radius

  • inferred — The identified mismatch affects per-pane verdicts and their downstream worker-attention projection. The inspected paths do not show a new network entrypoint, credential authority, or cross-tenant access.

Trust Boundaries and Controls

  • observed — The local listener checks the hook token before processing a request body; the relay requires POST and a matching token before forwarding accepted events. The PR’s test helpers exercise these existing boundaries rather than bypassing them.

Resilience and Maintainability Implications

  • inferred — For normal delivery, same-turn failures preserve the inferred cancellation, while a new prompt permits a new failure verdict. Relay interruption and reordered or duplicated envelopes remain unverified recovery states, not established failures.

Hardening Proposals

  • proposed — Verify the OpenCode 2 terminal-event-to-verdict path end to end, including its worker-attention result, and exercise cancellation-latch recovery with duplicated or reconnect-delayed relay events.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 65 functions across 51 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary change: displaying failed or stopped OpenCode turns and updating the missed screens.
Description check ✅ Passed The description is complete and structured. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, compatibility, and scope. Some checklist items remain unchecked…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: b46d188c-a619-4113-8591-5eed9613a5f3

📥 Commits

Reviewing files that changed from the base of the PR and between f6324f2 and 85d6e436e8cfee0bbb310fccfe27e4d22b779fb1.

📒 Files selected for processing (51)
  • docs/reference/agent-status-store.md
  • mobile/src/components/AgentSpinner.tsx
  • mobile/src/components/WorktreeListRow.tsx
  • mobile/src/host-screen/host-workspace-list.tsx
  • mobile/src/worktree/workspace-list-ordering.test.ts
  • mobile/src/worktree/workspace-list-ordering.ts
  • mobile/src/worktree/workspace-list-types.ts
  • src/cli/handlers/orchestration/worker-list-display-state.test.ts
  • src/cli/handlers/orchestration/worker-terminal-handlers.ts
  • src/main/agent-hooks/server-cancel-verdict-failure.test.ts
  • src/main/agent-hooks/server-opencode-normalization.test.ts
  • src/main/agent-hooks/server/server-cancel-verdict-latch.ts
  • src/main/ipc/dashboard-payload-validation.test.ts
  • src/main/ipc/dashboard-payload-validation.ts
  • src/main/opencode/hook-plugin-background-child-completion.test.ts
  • src/main/opencode/hook-plugin-lifecycle-delivery.test.ts
  • src/main/opencode/hook-plugin-opencode2-turn-ending.test.ts
  • src/main/opencode/hook-plugin-root-turn-state.test.ts
  • src/main/opencode/hook-service.test.ts
  • src/main/opencode/opencode2-turn-ending-captures.test-fixture.ts
  • src/main/opencode/status-plugin-delivery-source.ts
  • src/main/opencode/status-plugin-factory-source.ts
  • src/main/opencode/status-plugin-lifecycle-source.ts
  • src/main/opencode/status-plugin-message-preview-source.ts
  • src/main/opencode/status-plugin-ownership-source.ts
  • src/main/opencode/status-plugin-runtime-state-source.ts
  • src/main/opencode2/status-plugin-setup-source.ts
  • src/main/runtime/orchestration/worker-attention-context.test.ts
  • src/main/runtime/orchestration/worker-attention-context.ts
  • src/renderer/src/components/dashboard-popout/AgentKanbanCard.test.tsx
  • src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx
  • src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.test.ts
  • src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.ts
  • src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx
  • src/renderer/src/components/dashboard/DashboardAgentRow.tsx
  • src/renderer/src/components/dashboard/build-dashboard-snapshot.test.ts
  • src/renderer/src/components/dashboard/build-dashboard-snapshot.ts
  • src/renderer/src/components/dashboard/dashboard-card-bucket.ts
  • src/renderer/src/components/dashboard/dashboard-row-bucket.test.ts
  • src/renderer/src/components/dashboard/dashboard-row-bucket.ts
  • src/renderer/src/components/editor/ReviewNotesSendMenuContent.test.tsx
  • src/renderer/src/components/editor/ReviewNotesSendMenuContent.tsx
  • src/shared/agent-hook-listener/listener-state.ts
  • src/shared/agent-hook-listener/providers/opencode-family-events.ts
  • src/shared/dashboard-snapshot.ts
  • src/shared/orchestration-fleet-agent-status-evidence.ts
  • src/shared/orchestration-fleet-attention.test.ts
  • src/shared/orchestration-fleet-attention.ts
  • src/shared/orchestration-fleet-projection.test.ts
  • src/shared/orchestration-fleet-projection.ts
  • src/shared/orchestration-fleet-worker-projection.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

Comment thread src/main/opencode2/status-plugin-setup-source.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — one minor color-consistency note inline.

Reviewed changes

This run re-reviewed the rebuilt branch at 85d6e436 (8 commits, 51 files) since the prior pullfrog review at 3e383f77; it covers the reader surfaces #22944 did not reach.

  • Pop-out board and notes picker — dashboard cards carry an optional verdictMark (agentVerdictDisplayMark) beside the live dotState; it outranks the dot, is never muted by being seen, files the card under Done, and is accepted by the dashboard payload validator. The notes picker and the dashboard row's fallback label read the same mark.
  • Phone workspace dot — getWorktreeStatus folds its agent rows' verdicts (agentDotState) over the host rollup in desktop order (human wait > failed > working > interrupted), and the agent-attention sort ranks failed after a pending question.
  • Orchestration — worker stage evidence now carries the row's mainAgent; fleet attention re-derives one failure from it on every read, and orca orchestration worker-list prints failed / interrupted in the stage slot.
  • Phone stop color — the worktree-level spinner renders interrupted muted.

Verification: the OpenCode/root-turn, cancel-latch, fleet, worker-list, dashboard, picker and mobile suites pass (268 tests across the touched files); tc:web, tc:cli and mobile typecheck are clean. tc:node fails on a pre-existing main-only import in src/main/codex/codex-background-task-frames.ts, untouched here.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread mobile/src/components/AgentSpinner.tsx Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ The follow-up commit is clean; the earlier mobile-stop color note remains open inline.

Reviewed changes

This run reviewed the one commit pushed since the prior pullfrog review of 85d6e436 (5704fb4126).

  • Seen verdict cards settle into Idle — a new dashboardCardBucket moves a card whose verdict is already acknowledged (verdictMark set, dotState === 'done', unseen === false) into the Idle column, mirroring how a seen clean completion settles, while the dot keeps its Failed / Interrupted mark. An unseen verdict still files under Done, and a failure whose subagents still run (dotState !== 'done') stays under Done.
  • One bucket source — the pop-out patch and the dashboard row projection both call dashboardCardBucket, so the board and the in-app dashboard agree; dashboardCardDisplayState still returns the verdict mark first, so the settled card keeps its red dot.

Verification: the dashboard / kanban / patch suites pass (79 tests) and tc:web is clean.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run re-reviewed the single commit pushed since the prior pullfrog review of 5704fb41: 2183a990 makes a stale turn verdict stop colouring the phone's workspace-list dot, matching the desktop sidebar card, which reads only fresh rows.

  • Stale verdicts decay on the phone dot — getWorktreeStatus now skips an agent row whose updatedAt is older than the 30-minute window unless the host still owns it (structuredHostOwned), so a failed or stopped verdict past the window falls back to the host rollup instead of pinning the dot red/grey indefinitely.
  • Structured-host rows stay exempt — a structuredHostOwned row is still read regardless of age, mirroring the desktop isExplicitAgentStatusFresh gate that the sidebar card applies to those same rows. The human-wait branch is now under the same gate but keeps its previous effective freshness for non-structured rows.
  • Test — the old "keeps showing failed past the staleness window" case was replaced by a decay assertion for both a failure and a cancellation, plus the structured-host exemption.

Verification: mobile workspace-list-ordering, agent-row-display and workspace-list-sections suites pass (58 tests) and mobile tsc is clean.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run re-reviewed the single commit pushed since the prior pullfrog review of 2183a99: 711196f keeps a failed main agent's pending subagent question under Needs You on the pop-out board, instead of collapsing it into Done.

  • A question outranks a failure in the card column — dashboardCardBucket now derives the live bucket with the verdict removed and returns it unchanged when it is attention; a verdict otherwise files Done until seen then Idle, and a failure still outranks live subagent work. The dot still reads Failed through dashboardCardDisplayState, so the card sits under Needs You with its question shown and turns Failed once answered — matching resolveWorktreeStatus, where a pending question precedes hasFailed.
  • Tests — a patch-level case asserts a waiting row with a failed mainAgent stays bucket: 'attention' with its askSummary and verdictMark: 'failed'; two row-projection cases cover the same precedence and the failure whose subagents went silent, which files like a completion (Done until seen, then Idle).

Verification: the dashboard row-bucket, pop-out patch and build-snapshot suites pass (46 tests).

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-status-failed-turn-ending branch from 711196f to 9b08c05 Compare September 29, 2026 06:31

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run re-reviewed the three commits pushed since the prior Pullfrog review of 711196f: the mobile workspace-list dot is reverted, fleet failure attention now keys on the shared display mark, and a parity test pins the pop-out card to the desktop row dot and worktree ladder.

  • Mobile workspace-list dot reverted — e26986a removes the phone's hand-rolled getWorktreeStatus verdict fold and restores the six mobile files to main; the host will derive and publish the worktree display status in a follow-up after #23467. The branch no longer touches mobile/, and the one open Pullfrog thread (mobile stop color) is retired as moot.
  • Fleet failure keys on the display mark — f0e2c0a changes isFleetMainTurnFailed to agentVerdictDisplayMark(...) === 'failed'. This is behaviour-preserving: agentVerdictDisplayMark returns 'failed' exactly when the underlying verdict is 'failure', so the two predicates are equivalent. Its invariant test is now table-driven over every status × outcome.
  • Pop-out / desktop parity test — 9b08c05 adds dashboard-card-bucket.test.ts, running single-agent scenarios (clean, failed, stopped, live or questioning subagents, working, monitoring, waiting, blocked; each seen and unseen) through both the card projection and the desktop row dot + resolveWorktreeStatus ladder, asserting the card dot equals the row dot and the column follows the worktree status. A real oracle, not a tautology.

Verification: dashboard-card-bucket.test.ts (22) and orchestration-fleet-attention.test.ts (7) pass.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run followed up on the single commit pushed after the prior review at 9b08c05: afb12f1 hardens the fleet attention test.

  • Fleet failure pinned to the display mark under any row state — the status × outcome table in orchestration-fleet-attention.test.ts now also iterates every AGENT_STATUS_STATES value as the combined row state and asserts isFleetMainTurnFailed(mainAgent) equals agentVerdictDisplayMark({ state: rowState, mainAgent }) === 'failed'. That invariant holds because the 'failed' mark ignores the row state (only the cancellation arm reads it), and it documents that the fleet path — which never sees a combined row state — derives its failure from the main agent's record alone.

Verification: orchestration-fleet-attention.test.ts (7) passes.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Include OpenCode 2 in root-state verdict resolution. · opencode-family-events.ts:88-92

src/shared/agent-hook-listener/providers/opencode-family-events.ts:88-92
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include OpenCode 2 in root-state verdict resolution.

OpenCode 2 converts failed executions and user interruptions into session.error data before emitting idle. The shared plugin carries that verdict on the root SessionIdle payload. This guard discards it because source is opencode2, so the published status lacks mainAgent.outcome and legacy interrupted.

Suggested fix
   const mainAgent =
-    source === 'opencode'
+    (source === 'opencode' || source === 'opencode2')
       ? resolveOpenCodeMainAgent(state, paneKey, eventName, hookPayload)
       : undefined

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8dd098b3-b0b1-45b2-81f8-f9bff0f33b89

📥 Commits

Reviewing files that changed from the base of the PR and between 711196f6ba79e960d518521a85e9ddebcaa96900 and 2cd080346483b8b6d7ea66d29f5880387aa42acc.

📒 Files selected for processing (4)
  • src/main/agent-hooks/server-cancel-verdict-failure.test.ts
  • src/renderer/src/components/dashboard/dashboard-card-bucket.test.ts
  • src/shared/orchestration-fleet-attention.test.ts
  • src/shared/orchestration-fleet-attention.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run re-reviewed the single commit pushed since the prior Pullfrog review of afb12f1: 2cd0803 adds one test.

  • Same-prompt resend pins a new turn — server-cancel-verdict-failure.test.ts gains lets a failure through when the user resends the same prompt after the stop on the three-lane table (relayed Claude, Grok, OpenCode): after Ctrl+C, resending the identical prompt and then posting the provider failure must read mainAgent.outcome: 'failure', not the latched cancellation. It is falsifiable (the latch's keepLatchedCancellation would make it 'cancellation') and not redundant with the existing first/second case, since the identical prompt makes restatesAnotherPrompt return false and the release then rides a different path (Claude via opensNewTurn; Grok/OpenCode via hasExplicitPrompt). No production code changed; the file passes (14 tests).

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

… ended

An OpenCode turn that died on a provider error, or that the user stopped,
read as a clean Done: the plugin dropped `session.error` and the host
published no main-agent record, so the Failed and Interrupted marks main
already draws for Claude and Grok never applied.

The plugin now sends the root session's own state (`root_state`) and the
name of the error that ended its turn (`root_turn_error_name`) beside its
pane fold. The listener turns them into `mainAgent`: `MessageAbortedError`
is a cancellation, any other name a failure. A plugin that predates the
fields publishes no `mainAgent`, so nothing is inferred from the fold.
…the user

OpenCode 2 has no `session.error`. Its only turn verdict is the run-ending
event, and the setup bridge mapped `session.execution.failed` and
`session.execution.interrupted` to the same plain idle as a success, so a
failed or stopped OpenCode 2 turn read as Done.

The bridge now restates that ending as a `session.error` before the idle: a
failed execution by its error type (or `UnknownError`), a user's interrupt as
`MessageAbortedError`. Other interrupt reasons (shutdown, superseded,
inactivity) stay a plain idle. A test replays event sequences captured from
OpenCode 2.0.14 through the generated plugin.
…ts a provider failure

A user who pressed Ctrl+C on a relayed (SSH) Claude, Grok or OpenCode turn
saw the row flip from Interrupted to Failed when the aborted request then
reported its error (`StopFailure`, `stop_failure`, `session.error`). Those
listeners never learn of the cancel Orca inferred from the keystroke, so the
failure arrives as the provider's own verdict and the store's cancel hold
released on any settled main-agent record.

A failure no longer releases the hold: it ends the same turn the user already
stopped, so the row keeps the cancellation and no completion stamp. A new
prompt still opens a new turn whose failure shows as Failed, and a plain
settle still releases the hold. The tests also pin that a Ctrl+C at an
OpenCode root that already finished, while a busy child holds the pane open,
cancels nothing.
… failure attention

When a worker's agent turn failed (a Claude API error, an OpenCode provider
error) and the worker never reported done, the fleet showed it as a quiet
`done` (or `working` while a subagent ran) with no attention, so the
coordinator was never told.

The fleet evidence now keeps the row's `mainAgent`, the worker's stage carries
it (an optional wire field; older hosts send none), and attention adds one
`failure` whenever the main agent's latest turn failed, re-derived from that
record on every read so the next turn clears it. A restored, unconfirmed row
or an identity-only row contributes no record.
…worker-list

`orca orchestration worker-list` printed the folded activity, so a worker
whose turn failed read `[running/done]`. The stage slot now shows `failed` or
`interrupted` from the worker's main-agent record through the same verdict
rule the app uses. A worker with no fresh row keeps `unknown`, and a
main-agent state this build cannot name carries no verdict.
…st dot

The phone's workspace list dot read only the host's rollup, which has no
failed state, so a worktree whose agent failed showed a green Done dot (or
the yellow spinner while a subagent ran); only the expanded agent row was red.
A user-stopped worktree read green Done too.

The dot now folds in the verdicts its agent rows already carry, in desktop's
container order: a pending question first, then failed, live work,
interrupted, and the host rollup. Agent-attention sort ranks failed right
after a pending question and a stop with no signal. A host that hides the
worktree keeps it inactive.
…d and notes picker

Three desktop surfaces still read the folded row state where #22944 already
draws the main agent's verdict on the agent's own row. The pop-out agent
board filed a failed agent under Done with a green dot (or under Working
while subagents ran) and showed a user stop as a green Done; the review-notes
"send to agent" picker labelled a failed agent Done or Working; and a
dashboard row with no captured prompt labelled a failed agent from the fold.

Dashboard cards now carry the verdict mark (`failed` / `interrupted`, from
`agentVerdictDisplayMark`) as an optional field. It outranks the live dot,
is never muted by being seen, and files the card under Done, since a failure
ranks like a completion. The picker and the fallback label draw the same mark.
… seen completion

The pop-out board kept every failed or user-stopped card in the Done column
until that agent started another turn, even after it was seen, while a seen
completion settles into Idle. A failure ranks like a completion for attention
and a stop is not news, so a seen verdict on a finished row now settles into
Idle too; its dot keeps the Failed or Interrupted mark. A failure whose
subagents still work stays under Done.
… the desktop card

The desktop worktree card reads only fresh agent rows, so a failed or stopped
turn older than the staleness window stops colouring its dot. The phone list
dot kept a stale failure red, and on hosts without desktop Smart Sort ranks it
pinned that worktree above live work until the agent's next turn.
…ain agent failed

The pop-out column read the card's display state, where a failed main agent
outranks a waiting row, so a subagent's pending question moved to Done with its
ask summary dropped. The column now follows main's attention rule (a question
first, as the worktree card and Smart Sort rank it); the dot keeps the Failed
mark. A failure whose subagents went silent files like a completion.
The phone's workspace list dot re-derived the desktop worktree card's
precedence ladder and freshness fold by hand on the phone, and each review
loop found a new way that copy drifted from the desktop. It also aged host
rows on the phone's clock. The list dot returns to main's behaviour; the
phone's agent rows already show Failed / Interrupted (#22944).

A follow-up will have the host derive the worktree's display status once,
from the same ladder the desktop card uses, and publish it to the phone,
after #23467 changes the same verdict vocabulary.

No other mobile change in this branch is needed, so the branch no longer
touches mobile/.
…ed mark

A worker's fleet failure attention compared the main agent's raw outcome to
'failure'. Key it on the display rule instead (agentVerdictDisplayMark ===
'failed'), so any verdict the agent rows draw as Failed also raises fleet
failure attention and the two cannot drift when the verdict vocabulary grows.
It still re-derives from the record on every read, so the next turn clears it.
…tree ladder

One table of single-agent scenarios (clean, failed, stopped, failed or
stopped with live subagents, failed with a subagent's question, working,
monitoring, waiting, blocked; each seen and unseen) runs through the
snapshot builder's card projection and through main's own desktop functions
(the agent-row dot, applyAgentPaneActivityFlags, resolveWorktreeStatus). It
asserts the card dot matches the row dot, and the column is Needs you
exactly when the worktree card reads permission, Working exactly when it
reads working or monitoring, and otherwise Done until seen, then Idle.
…row state

The fleet sees only the main agent's record, so its failure attention must
match the agent display's Failed mark whatever the combined row reads
(subagents working or asking). The table fed the main agent's own state as
the row state, so a display rule that began depending on the combined state
would have passed; it now crosses every row state with every main-agent
record.
…aunch entry too

A pane opened from Orca's OpenCode 2 entry runs the opencode2 plugin variant, which already
sends the root session's state and turn error, but the listener only read them from the
opencode variant, so a failed OpenCode 2 turn launched that way still read Done. Live QA
confirmed the same plugin reports reach the host on that route; mimo-code stays unread.
…nCode 2 launch entry's hook path

The listener change that admits opencode2 was pinned only by a normalizer unit test; this runs
the same inferred-cancel, same-turn failure, new-turn failure and plain-settle cases through the
hook server on /hook/opencode2.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/agent-status-failed-turn-ending branch 2 times, most recently from 8a2becb to 39bbf8a Compare September 29, 2026 07:20

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run re-reviewed the single commit pushed since the prior pullfrog review at 2cd0803: ef7aa83 also reads OpenCode 2's root turn state, so a failed or stopped turn from Orca's OpenCode 2 launch entry shows Failed / Interrupted.

  • OpenCode 2 launch entry now publishes the verdict — normalizeOpenCodeFamilyEvent resolves mainAgent for source === 'opencode' || source === 'opencode2' (was opencode only), so the opencode2 plugin variant that posts to /hook/opencode2 is no longer dropped; mimo-code stays excluded. This closes the gap the coderabbit thread flagged, and the shared setup bridge already published root_state / root_turn_error_name on that route.
  • Tests split and widened — the old opencode2 | mimo-code "publishes no main agent" case is split: opencode2 now asserts a failure verdict, mimo-code still asserts undefined. hook-plugin-opencode2-turn-ending.test.ts now replays its real OpenCode 2.0.14 captures through both generated plugin variants (getOpenCodePluginSource → /hook/opencode, getOpenCode2PluginSource → /hook/opencode2) and asserts each posts to its own route, so the branch is exercised end to end rather than only at the normalizer.

Verification: server-opencode-normalization.test.ts (43) and hook-plugin-opencode2-turn-ending.test.ts (22) pass — 65 tests.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

This run re-reviewed the commits pushed since the prior pullfrog review at ef7aa83 (the branch was rebased onto main, so every SHA changed): the OpenCode 2 launch-entry fix itself, plus a test-only follow-up.

  • OpenCode 2 launch entry reads its own root verdict — normalizeOpenCodeFamilyEvent resolves mainAgent for source === 'opencode' || source === 'opencode2', so the opencode2 plugin variant that posts to /hook/opencode2 is no longer dropped; mimo-code stays excluded. Same content as ef7aa83, re-verified after the rebase.
  • Cancel-then-failure lane for the OpenCode 2 launch entry — server-cancel-verdict-failure.test.ts gains an openCode2LaunchEntry lane (agentType: 'opencode2', posting to /hook/opencode2, reusing the OpenCode payloads) in its describe.each, so the hold / keep-cancellation rules run through that path too. It is falsifiable against the fix: without opencode2 in the guard, the "new prompt" cases cannot read mainAgent.outcome: 'failure', because the row would carry no main agent at all.

Verification: server-cancel-verdict-failure.test.ts (18), server-opencode-normalization.test.ts (43) and hook-plugin-opencode2-turn-ending.test.ts (22) pass.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: ready for your review (not merged)

The problem. When an agent's turn ends because of an error (the model request is rejected, the context overflows) or because you press Esc, some Orca screens still showed it as a clean green "Done". #22944 (on main since 09-28) already fixed this for Claude and Grok on the sidebar, tabs, worktree status, notifications and phone agent rows. OpenCode never reported how its turn ended, and a few screens never read the verdict.

What changes for you.

  • OpenCode (1.x, and 2.x whether run as opencode or from Orca's own OpenCode 2 entry): a failed turn now shows Failed with "OpenCode failed"; an Esc shows Interrupted. A clean turn still shows Done, and a failure does not stick to the next turn.
  • Pop-out agent board: a failed or stopped card keeps its mark. Unseen, it sits under Done; once seen, it settles into Idle but keeps the Failed or Interrupted dot, where main showed a grey Idle.
  • "Send notes to agent" picker: a failed agent is labelled Failed.
  • Orchestration: a worker whose turn failed raises one failure attention, and worker-list shows failed. Both clear on the next turn.
  • If you stop a turn and the aborted request then reports its error, the row stays Interrupted. A failure in your next turn, even with the same prompt text, still shows Failed.
Before (main) After (this PR)
OpenCode 400 on main OpenCode 400 on this branch
pop-out board on main pop-out board on this branch

What happened in review.

Validation.

  • Full typecheck, mobile typecheck and test ratchet, oxlint and the changed-code quality gate are clean. Every production change was checked by deleting it and confirming its test fails.
  • CI on 39bbf8a80f9 is green: all unit shards, typecheck, static analysis, relay, packaging and e2e.
  • Live Electron QA ran on a separate Mac with hidden windows and a throwaway home folder, using real OpenCode 1.18.33, OpenCode 2.0.14 and Claude Code against a mock model server. Before and after were compared, and every scenario in the PR body's table passed. The Grok QA seat had no quota, so a Claude agent ran the QA.
  • Readiness checklist, start and end: the end run passes all 8 items with no blockers.

Not verified live: OpenCode over SSH (unit-tested through the relay), Windows and Linux, the orchestration worker list and fleet attention (unit-tested), and the phone (unchanged by this PR).

Before merging, you should know:

  1. Until fix(agent-status): a turn a crash cut off reads Interrupted, an unproven end Couldn't confirm #23467 lands, a stopped turn on the pop-out board uses the shared status dot, which main still draws red. fix(agent-status): a turn a crash cut off reads Interrupted, an unproven end Couldn't confirm #23467 makes it muted everywhere, so landing fix(agent-status): a turn a crash cut off reads Interrupted, an unproven end Couldn't confirm #23467 first gives the cleanest result.
  2. A failed turn's red mark does not fade when you look at it (on the worktree card it lasts until the next turn, or 30 minutes of inactivity). That's main's existing rule from fix(native-chat): a request that failed reads as failed #22944, unchanged here.
  3. Notifications from Orca's OpenCode 2 entry say "opencode2 failed", using the internal agent id. main already says "opencode2 finished" for that entry, so the label is not from this PR.
  4. Follow-ups outside this PR: a phone workspace-list dot derived on the host (after fix(agent-status): a turn a crash cut off reads Interrupted, an unproven end Couldn't confirm #23467); Codex turn failures (fix(agent-status): settle a Codex turn that failed, from its session log #22753, to be restacked on this); showing the error's reason text.
  5. Bot findings not caused by this PR: an Android update-check note in mobile/src/app-update/ (not in this diff), and an outdated thread on activity-clear-completed.ts (left on the pre-rebuild code; that file is no longer changed).

Not merged. Merging is your call.

nwparker added a commit that referenced this pull request Oct 2, 2026
Adapt the root verdict proposal from brennanb2025 in PR #23105 to the current TUI-owned lifecycle, keeping hook-store authority and existing mainAgent semantics.
nwparker added a commit that referenced this pull request Oct 2, 2026
…tion (#24612)

* fix(opencode): retain failed and stopped TUI turn outcomes

Adapt the root verdict proposal from brennanb2025 in PR #23105 to the current TUI-owned lifecycle, keeping hook-store authority and existing mainAgent semantics.

* fix(opencode): let auto-approved permissions settle before attention

* fix(opencode): reconcile cached outcomes with completed session turns

* fix(opencode): bind terminal verdicts to ending event timestamps

* fix(opencode): publish approval cards for permission requests
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing this combined patch. #24612 already records OpenCode failures and cancellations; the remaining board, picker and worker-list display gaps should be handled separately on current main.

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.

1 participant