Skip to content

fix(opencode): clear an opencode run row when its process exits - #24292

Closed
brennanb2025 wants to merge 9 commits into
mainfrom
brennanb2025/opencode-run-clear-on-exit
Closed

brennanb2025 wants to merge 9 commits into
mainfrom
brennanb2025/opencode-run-clear-on-exit

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​652 $\color{#cf222e}{\Huge{\mathbf{−}}}$​65 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​587
Prod 21 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​149 $\color{#cf222e}{\Huge{\mathbf{−}}}$​56 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​93

ELI5

When you run opencode run "<prompt>" in an Orca terminal, OpenCode does the prompt and then exits back to your shell. While it runs, the pane should show Working. Once it has exited, nothing is running there, so the pane should show nothing, the same as when any other agent exits. Instead Orca sometimes left a Done row behind for a command that had already ended. Now the row clears when the command exits, and you still get the "finished" notification, the same one you got before.

What it fixes and scope

  • Fixes: a stale Done row (sidebar, orca worktree ps, mobile) left behind after an OpenCode 2 opencode run exits. Regression from fix(opencode): report OpenCode 2 status from each pane's own TUI #23722, which added opencode run status and posted Done when the command ended.
  • Rule this follows: agent Done means the agent is still running and has finished its turn. When the agent's process exits back to the shell, the pane's row goes away. Orca already does this for every other agent: the renderer drops the row when the command finishes, and main's ended-process cleanup clears host rows.
  • Keeps: the completion notification. On main, the run's Done was also what fired the "finished" notification (desktop banner, the worktree's unread dot, phone push), including for runs in hidden tabs. For opencode run the exit is the only "finished" signal, so removing the Done alone would have silently removed that notification. This PR keeps it without a row.
  • Not affected: full OpenCode windows, which report through their own plugin; other agents' rows; and how opencode run is detected or shown as Working.

What Changed

Before (user): after opencode run "…" exited, the pane's row sometimes stayed as Done and sometimes disappeared. Which one depended on whether something had recently listed terminals (orca worktree ps, orca terminal list, orchestration, the phone refreshing, automations). When the Done stayed, you got a "finished" notification; when it vanished, you got none.

After (user): Working while the run is going, then no row once it exits, in the sidebar, orca worktree ps and mobile. That holds for normal exits and Ctrl+C, in visible and hidden tabs, with or without a recent terminal list. Each run that showed Working gives one "finished" notification ("stopped" after Ctrl+C), with the same focus rules, unread dot and phone push as any agent's turn ending, including when its tab was hidden before it finished. A run started in a tab that is already hidden behaves as on main: it shows no Working (#24238 adds that), and any notification comes from the pane's own exit check, as before.

Also changed for OpenCode 1 opencode run (which loads OpenCode's plugin, so the plugin reports its row): its row now also clears when the process exits, instead of keeping the plugin's Done. Its notification is unchanged: the plugin's own, with its message preview, sent once.

Mechanism, before:

  • fix(opencode): report OpenCode 2 status from each pane's own TUI #23722's detector (opencode-run-lifetime-status.ts) wrote a Done row into the host status store when the command finished.
  • If no terminal list had happened, that Done stayed in the store every reader uses.
  • If one had, the command end first retired the pane's launch authority, which deleted the row without telling the renderer, and the Done was then refused. So the outcome depended on timing.

Mechanism, after:

  • At the end of an armed run (the command finished, or the next command started first), the detector posts no status. It clears the pane through the store's existing ended-process cleanup, reconcileEndedProcessForPaneKeys (server-cleanup.ts), which sends the pane clear every reader receives. It keeps a resume identity only if the row carried one (a plugin-owned OpenCode 1 run does; an OpenCode 2 run has none), as the renderer's confirmed-shell path does for other agents, because the shell outlived the run.
  • The detector now runs before launch-authority retirement, in both the visible path and the hidden-tab daemon path, so the clear reaches the renderer whether or not a terminal list happened.
  • For the notification, the detector also emits a new agent-run-ended terminal side-effect fact (agent type, and interrupted for exit code 130). The renderer hands it to the same completion notifier a hook Done uses, so focus suppression, the unread dot, the cooldown, phone push and wording all match. It only completes a turn the notifier still has open and no hook Done is already finishing, so a plugin-owned OpenCode 1 run, or a pane whose own process-exit check also saw the exit, still notifies once. Paired remote desktop clients map the host's pane id to their own with the same rule their mirrored status rows use.
  • The pane's prompt state is reset at the clear, as the old Done did.
  • Removed code that existed only to post or order the Done: the after-facts deferral, the SIGINT exit-code constant and the exit-code argument.

Why

  • Same rule as every agent. A Done for a process that has exited tells you something is waiting for you when nothing is. Reusing Orca's existing ended-process cleanup means opencode run follows exactly the path other agents take when their process exits.
  • Notification kept on purpose. The rule is about rows. Other one-shot runs (claude -p, OpenCode 1 run) notify through their hook Done before they exit; an OpenCode 2 run has no hook, so the exit has to carry it.
  • Rejected: keep the Done (closed fix(agent-status): keep an opencode run's Done when its command end retires the pane #24252). That would have made opencode run the only agent whose row outlives its process.
  • Rejected: post Done, then clear. The clear is sent at once and status is batched after it, so the renderer could get the Done after the clear and keep it, which is the bug class this PR removes.
  • Rejected: a field on the existing command-finished fact. A run superseded by the next command has no command-finished, and main notified in that case.

Differences from the usual mechanism, labelled

Known limits, not in this PR

  • A paired remote desktop client can miss the notification for a run that exits within about 50–250 ms of first showing Working (the host batches tab updates). Fixing it would reopen the double-notification risk.
  • An older desktop client paired with a newer host ignores the new fact, so it gets no opencode run notification from that host. No row ends up wrong.

Linked Issue

Follow-up to Linear STA-8629 and #23722 (regression source). It should ship with the #23722 cherry-pick, STA-9065.
Merge note: #24238 (open) edits the same detector and has two tests that expect Done on exit (opencode-run-lifetime-runtime.test.ts, "posts Working from the daemon's command-started fact and Done from its command-finished" and "posts Done from main's own scanner once the tab is shown again"). Whichever lands second flips them to "Working, then the row is cleared"; the test file conflicts in createRuntime (return both statusStore and getForegroundProcess). No production conflict.

Visual Proof

Live QA on a remote Mac with real OpenCode 2.0.21 in Orca terminal panes, main (3135fbb) against this PR (round 1 at fe1e542 for rows; round 2 at b15c250 for rows plus notifications; later commits only add an OpenCode 1 notification guard, a pane-key helper reuse, an explicit switch case and a merge of main). macOS blocked desktop banners for the unsigned dev build on both arms, so notifications were counted from the renderer's notification requests.

Case main this PR
Visible run, nothing listed terminals first stale Done stays (sidebar and worktree ps), 1 notification no row, 1 notification "OpenCode finished"
Visible run after orca worktree ps row vanishes, no notification no row, 1 notification
Ctrl+C mid-run stale interrupted Done, 1 notification no row, 1 notification "OpenCode stopped"
Second run in the same pane stale Done, 1 notification no row, 1 notification
Started visible, tab hidden before it ends stale Done plus unread dot, 1 notification no row, unread dot, 1 notification
Started in a hidden tab no row, 1 notification (the pane's own exit check) no row, 1 notification (2 of 3 tries; the first try sent none and did not reproduce)
Tab hidden more than 5 minutes no row, no notification no row, no notification, no phone push
Interactive OpenCode TUI, one turn then quit Done at turn end, row clears after quit same

Main: a stale Done stays after the run exited.
main: stale Done after exit

This PR: Working while the run is going.
PR: Working during the run

This PR: no row 60 s after the run exited.
PR: no row after exit

This PR: no row after Ctrl+C.
PR: no row after Ctrl+C

This PR: no row after a run following orca worktree ps.
PR: no row after a listing

Testing

  • I manually tested these changes (live QA on a remote Mac with real OpenCode 2, see Visual Proof)

  • Automated tests added/updated, or explained why not below

  • Runtime tests in opencode-run-lifetime-runtime.test.ts, wired to the real status store: visible and hidden panes, normal and Ctrl+C exits, the terminal-list timing in both paths, a run superseded by the next command, a plugin-owned OpenCode 1 run (row cleared, resume identity kept), and a non-run pane left alone. They also assert the agent-run-ended fact.

  • Renderer tests: agent-run-ended-notifications.test.ts (one notification, Ctrl+C reads stopped, a plugin Done in its quiet window keeps its own payload and id, a newer turn cancels the old one), agent-run-ended-cross-lane.test.ts (one notification when the pane's own process-exit check also fires, both orders), and remote-terminal-side-effect-batch.test.ts (remote pane-key mapping).

  • Ablation: with the row-clearing change reverted, 13 of 35 OpenCode tests fail; reverting each piece of the notification change (the fact, the routing, the open-turn guard, the quiet-window guard, the shared per-pane record) fails its tests.

  • Related suites pass (ended-process cleanup, orca-runtime.test.ts, daemon hidden-pane events, PTY-exit cleanup, the renderer's late-Done, process-exit, hook-completion and notification-dispatch tests). pnpm tc:node, pnpm tc:web and the changed-code quality check pass.

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

  • Security: no new inputs.
  • Backwards compatibility: one new optional terminal side-effect fact kind inside the existing event; older clients skip unknown kinds without affecting the rest of the batch. No persisted change.
  • Performance: no per-chunk cost; one store scan per armed run end.

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)

Done means an agent's TUI is still alive and finished its turn. When
`opencode run` (the OpenCode 2 one-shot) exits back to the shell nothing
runs in the pane anymore, but the run detector posted Done into the host
status store at command end. Whether that Done survived depended on
timing: after a terminal-list refresh the pane's launch authority was
retired on command end and the Done was refused, otherwise a stale Done
stayed in the sidebar, `orca worktree ps` and mobile.

At command end the detector now posts no status. It retires the row it
created through the store's ended-process reconciliation
(reconcileEndedProcessForPaneKeys, keeping resume identity because the
shell outlived the run), which emits the pane clear every reader gets.
It runs before launch-authority retirement, which deletes rows without
telling the renderer, so visible and hidden panes clear alike.

Removes what existed only for the run's Done: the interrupted flag on
Ctrl+C exits and the after-facts deferral that ordered the Done behind
the command-finished fact.
…ith no row

Clearing the row when `opencode run` exits removed the run's Done, and
that Done was what announced the finished run: the desktop notification,
the worktree unread marker and the phone push. OpenCode 2's `run` loads
no plugin, so its exit is its only turn end. Without the Done a run in a
hidden or background tab, or one that ended before the visible pane's
first process poll, finished silently, while hook-reporting one-shots
still notified through their own Done.

The run detector now announces the ended run as a terminal side-effect
fact (`agent-run-ended`, with Ctrl+C read as interrupted) beside the
row clear, on the visible and hidden command-finished paths and when a
new command supersedes the run. The renderer routes it, before the
per-PTY consumer lookup, into the pane's existing hook-lane completion
coordinator, so focus suppression, unread and mobile push follow the
same policy as a hook Done and no status row is written. It completes
only a turn that lane saw open, so an OpenCode 1 `run`, whose plugin
Done already announced the turn, gets one notification. Paired clients
receive the host's facts with host tab ids; they are now mapped to the
mirrored pane keys the client's status rows use.

Also ends the runtime's prompt lifecycle with the run, so a later
prompt send is no longer verified as if the pane still held a working
agent, and pins that a hook-owned run keeps its resume identity.
…rror tab shape

The OpenCode 1 dedupe test now lets the process exit two seconds after
the plugin's Done was announced, the case that would notify twice
without the open-turn guard. The mirrored pane-key test builds a ready
terminal surface so it typechecks.
A visible pane's own process monitor also completes the run when the
process exits. Pin that the ended-run announcement and that
process-exit completion share the pane's completion identity, so
whichever fires first, the other adds nothing.
… in its quiet window

An OpenCode 1 `run` loads its plugin, which posts its own Done with the
prompt and the assistant's last message. The process exits right after,
so the run-ended announcement usually lands inside that Done's 1.5 s
quiet window. The open-turn guard let it through, and the pending
payload was swapped for the announcement's empty one: the notification
body lost its message preview ("OpenCode finished." instead), and the
notification id moved to the renderer's clock, so acknowledging the row
no longer cleared it.

The run's end now completes a turn only while no hook Done is pending.
A pending Done already completes that turn, as main's plugin Done did.
A newer turn that cancels the pending Done still drops it, as for any
hook agent. OpenCode 2 never has a pending hook Done and is unchanged.
…own rule

The remote side-effect batch re-derived the mirrored pane key that the
mirrored status rows get from toMirroredPaneKey. If either copy changed
alone, an ended-run announcement from a remote host would name a pane no
hook-lane coordinator knows, and the notification would silently stop.
toMirroredPaneKey now takes just the tab and leaf it reads, and the
batch mapping calls it.
The type-aware code-quality audit requires the fact switch to cover
every fact kind. `agent-run-ended` is announced once per batch before
the consumer lookup, so consumers take no action on it; say so with an
explicit empty case.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: ready for merge review (not merged)

The problem. After opencode run "<prompt>" exited back to the shell in an Orca terminal, the pane could keep a Done row in the sidebar, orca worktree ps and mobile, even though nothing was running there anymore. Whether it stayed or vanished depended on whether something had recently listed terminals. The cause was #23722's opencode run detector, which posted Done when the command ended.

What changes for you. While the run is going the pane shows Working; once it exits, the row is gone everywhere, the same as when any other agent exits. That holds for normal exits and Ctrl+C, with or without a recent terminal list, and in tabs hidden before the run ends. You still get one "OpenCode finished" notification per run ("OpenCode stopped" after Ctrl+C), with the usual focus rules, unread dot and phone push. For OpenCode 1 opencode run (plugin-reported), the row now also clears on exit, and its notification is unchanged.

Fixed during review

  • R2-F1 (P2): the original change dropped the "finished" notification, because on main the Done was what fired it, including for hidden tabs. It is now restored without a row, through a new agent-run-ended terminal fact routed to the same notifier a hook Done uses (24a0883).
  • R2-F2 (P3): the pane's prompt state stayed "working" after the run exited; it is now reset at the clear (24a0883).
  • R6-1 (P3, introduced by the review's own fix): for an OpenCode 1 run that exits right after its plugin's Done, the notification lost its message preview and its id. It now keeps the plugin's own payload (ab541d5).
  • R4-1 (P2, tests): added a test for a plugin-owned OpenCode 1 run, which also pins that a resume identity is kept when the row has one (24a0883, 54f85b1).
  • R3-F1 (P3): the comment about keeping resume identity now says it is kept only when the row carries one (24a0883).
  • Readiness N-1 (P3): the remote pane-key mapping reuses the status rows' existing helper instead of a copy (27517ee).
  • The new fact kind was missing from an exhaustive switch in the renderer's fact handler, caught by CI's type-aware lint (3f1a83e).
  • Main merged twice to keep CI running; the second merge includes refactor(native-chat): move the provider-exit proof out of structured-agent-session-adapter.ts so main's lint passes #24323, which fixes main's own static analysis failure (66e1f86).

Deferred (P3, recorded, not fixed here)

  • A paired remote desktop client can miss the notification for a run that exits within about 50–250 ms of first showing Working, because the host batches tab updates. Fixing it would reopen the double-notification risk.
  • An older desktop client paired with a newer host ignores the new fact, so it gets no opencode run notification; no row ends up wrong.
  • The clear covers the whole pane, so a background agent job's row in the same shell is also cleared at run end; it comes back on that agent's next event. Main overwrote it with Done anyway.
  • Test nits: two producer tests don't assert the fact; the cross-lane test covers a visible pane and the process-exit path only.

Follow-ups, not caused by this PR

Verified

  • Review: four parallel reviewers on the first round (store side, event ordering and renderer, mechanism and architecture, tests), two fresh reviewers on the notification fix, a narrow check of each later fix, and a readiness checklist on the final diff (all 8 items pass, no P0–P2).
  • Tests: the OpenCode runtime and detector tests, the new renderer notification tests, and the related suites (ended-process cleanup, orca-runtime.test.ts, daemon hidden-pane events, PTY-exit cleanup, hook-completion and notification-dispatch tests) pass. Reverting the row-clearing change fails 13 of 35 OpenCode tests, and reverting each piece of the notification change fails its tests. pnpm tc:node and pnpm tc:web pass. CI on the final head 3f1a83e is green (17 pass, the rest skipped by path filters).
  • Live QA on a remote Mac with real OpenCode 2.0.21 in Orca terminal panes, main (3135fbb) against this PR (fe1e542, then b15c250). Main left a stale Done after a visible run, Ctrl+C or a second run. This PR left no row in every case (sidebar and worktree ps, rechecked 60 s later) and sent exactly one notification per run, never two. An interactive OpenCode TUI turn behaved the same on both.

Main: a stale Done stays after the run exited.
main: stale Done after exit

This PR: Working while the run is going.
PR: Working during the run

This PR: no row 60 s after the run exited.
PR: no row after exit

This PR: no row after Ctrl+C.
PR: no row after Ctrl+C

Not verified

  • macOS blocked desktop banners for the unsigned dev build on both arms, so notifications were counted from the app's notification requests, not seen as banners. Phone push was not tested on a real phone.
  • The commits after b15c250 (an OpenCode 1 notification guard, the pane-key helper reuse, the explicit switch case and a merge of main) were not rebuilt for live QA; they don't change OpenCode 2 behaviour and are covered by tests and CI.
  • One run started in a hidden tab sent no notification on the first try; two fresh repeats sent one each, and the code shows no path that would drop it. The cause of that one miss is unknown.
  • Not run live: Windows, Linux, SSH remotes, a paired remote desktop client, OpenCode 1, and a Claude TUI.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 1, 2026 09:42
@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Clears agent run rows when their process exits.

The PR should not merge until run-exit cleanup also removes a plugin-owned Done row from an unmounted renderer pane.

Findings

  1. P1 Plugin Done row remains visible ▶

Summary

This PR replaces the Done status written when opencode run exits with ended-process row cleanup and a separate completion-notification fact. It also forwards cleanup options through runtime wiring and maps remote side-effect batches into client pane identifiers. The host cleanup works for the tested rows, but a plugin-owned Done can remain in the renderer after its host row is cleared.

Reviews (1) · Last reviewed commit: "fix(renderer): name the ended-run fact i..."

Comment on lines +193 to +196
this.reconcileAgentStatusForEndedProcessFn?.(this.collectAgentStatusPaneKeysForPty(ptyId), {
preserveResumeIdentity: true
})
},

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 Plugin Done row remains visible

When an OpenCode 1 run reports Done through its plugin before exiting, this cleanup clears the host row. But the renderer ignores a pane-clear event while its row is Done. In a hidden tab without a mounted pane to perform its own exit cleanup, the sidebar can therefore keep showing Done after the process exits.

Knowledge Base Used: Agent integrations and hooks

@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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

🧰 Additional context used
📚 Code guidelines (1)
AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 1b4b09b8-05e4-45ab-9175-86a7b9bf4578

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6148e and 3f1a83e.

📒 Files selected for processing (27)
  • src/main/orcad/orcad-entry.ts
  • src/main/runtime/agent-status-store-wiring.test-fixture.ts
  • src/main/runtime/opencode-run-lifetime-runtime.test.ts
  • src/main/runtime/opencode-run-lifetime-status.test.ts
  • src/main/runtime/opencode-run-lifetime-status.ts
  • src/main/runtime/orca-runtime-emit-daemon-pty-transient-fact.ts
  • src/main/runtime/orca-runtime-get-unpersisted-tracked-title-for-pty.ts
  • src/main/runtime/orca-runtime-preserved-branch-cleanup.ts
  • src/main/runtime/orca-runtime-schedule-wait-blocked-check.ts
  • src/main/runtime/orca-runtime-state-fields.ts
  • src/main/runtime/orca-runtime-stop-requested-pty-ids.ts
  • src/main/runtime/runtime-terminal-state-records.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/renderer/src/components/terminal-pane/agent-completion-coordinator-types.ts
  • src/renderer/src/components/terminal-pane/agent-completion-coordinator.ts
  • src/renderer/src/components/terminal-pane/agent-completion-hook-observer.ts
  • src/renderer/src/components/terminal-pane/agent-run-ended-cross-lane.test.ts
  • src/renderer/src/components/terminal-pane/pty-connection-command-finished-late-done.test.ts
  • src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.ts
  • src/renderer/src/hooks/agent-hook-completion-notifications.ts
  • src/renderer/src/hooks/agent-run-ended-notifications.test.ts
  • src/renderer/src/hooks/agent-run-ended-notifications.ts
  • src/renderer/src/hooks/ipc-events/runtime-client-ipc-bridge.ts
  • src/renderer/src/runtime/remote-terminal-side-effect-batch.test.ts
  • src/renderer/src/runtime/remote-terminal-side-effect-batch.ts
  • src/renderer/src/runtime/web-session-tabs-sync/agent-status-primitives.ts
  • src/shared/terminal-side-effect-facts.ts
💤 Files with no reviewable changes (1)
  • src/main/runtime/runtime-terminal-state-records.ts

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


📝 Walkthrough

Walkthrough

The runtime now clears status rows for tracked ended runs and records agent-run-ended facts, including interruption state when applicable. The renderer maps remote batch identifiers and routes eligible, non-replay ended-run facts through agent completion handling. Reconciliation callbacks now forward options to preserve resume identity. Tests cover runtime status clearing, renderer notifications, duplicate completion paths, and remote identifier conversion.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 3f1a8

The change clears completed OpenCode run rows while retaining eligible completion notifications and resume identity. No actionable merge-blocking risk remains; live desktop-banner and phone-push validation remains limited.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 3f1a8

The inspected completion paths retain pane ownership and notification checks, and no security regression was established. The change separates status cleanup from notifications, but incomplete shutdown and end-to-end coverage limit confidence.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected new fact affects pane completion and notification state. It carries no prompt, credential, command, or executable action; its attribution comes from runtime PTY and leaf records. No additional privileged sink was established in this route.

Trust Boundaries and Controls

  • observed — Arming requires an observable local PTY, enabled status reporting, recognized foreground agent, matching command-line identity, and an OpenCode run command. SSH and WSL foregrounds are excluded from this detector.
  • observed — Remote translation namespaces PTY identity by environment and converts parseable host pane keys using the status-row mapping. Invalid pane keys are omitted. Completion additionally requires a live coordinator, previously observed Working state, and no pending hook-Done timer; replay batches are rejected.

Resilience and Maintainability Implications

  • observed — Status cleanup precedes the notification side effect and clears prompt-lifecycle state, avoiding continued interpretation of output as delivery to an ended Working run. Detector disposal cancels its timer and removes run state so late observations cannot announce that forgotten run.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 27 functions across 26 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: clearing an OpenCode run row when its process exits.
Description check ✅ Passed The description is comprehensive and covers the user impact, implementation, rationale, testing, visual proof, known limits, compatibility, and checklist items. It identifies related issues, although …
Linked Issues check ✅ Passed Issue #24252 is closed and marked completed, canceled, or archived. It supplies historical context only. Its acceptance criteria do not impose coding requirements on this pull request. No active direc…
Out of Scope Changes check ✅ Passed The changes support the reported OpenCode run lifecycle fix. They clear ended run rows, emit the agent-run-ended fact, preserve completion notifications, handle OpenCode 1 completion identity, updat…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@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.

Important

One notification path silently disappears when the terminalMainSideEffectAuthority kill switch is off and the run is in a parked/hidden local pane — the agent-run-ended fact is the only carrier now, and that pane type never subscribes to the fact channel. Details inline. A second, narrower note: preserveResumeIdentity: true is a no-op whenever the pane also has launch authority, because retirement deletes the retained remnant right after.

Reviewed changes

  • Clear instead of Done on run exit — OpenCodeRunLifetimeStatus.onCommandFinished now calls clearEndedRun then announceEndedRun instead of publishing a done row; the clear goes through reconcileEndedProcessForPaneKeys(..., { preserveResumeIdentity: true }) before launch-authority retirement, in both the visible chunk path and the hidden daemon path.
  • New agent-run-ended terminal side-effect fact — carries agentType and interrupted? (exit 130); the renderer announces it through the hook completion coordinator before the per-consumer lookup, so panes with no fact consumer still notify while the plugin Done / pane process-exit lane keeps it to one.
  • Remote mirroring — toClientTerminalSideEffectBatch remaps the host's pty/tab/pane ids to the client's mirrored ids, reusing the status rows' own pane-key rule.
  • Removed Done-only machinery — the afterFacts deferral, runAfterPendingTerminalSideEffectFacts, and the SIGINT exit-code plumbing.
  • Tests — runtime specs against the real status store (clear reaches the store, channel ordering, resume remnant, non-run panes), plus notification, cross-lane dedup, and remote-batch renderer specs.

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


// Why before the consumer lookup: the run's end must notify like a hook Done does, in panes
// that have no fact consumer (unmounted, never shown, or a visible remote-runtime pane).
function announceEndedAgentRuns(batch: TerminalSideEffectBatch): void {

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 ended-run notification now rides only the pty:sideEffect fact channel, and that channel is not always subscribed. ensureSideEffectChannelSubscription is only called from registerTerminalSideEffectFactConsumer, and with terminalMainSideEffectAuthority: false local panes and parked watchers deliberately do not register a consumer — so the renderer never calls window.api.pty.onSideEffect and this function never sees the batch. For a parked/hidden local pane the parked onCommandFinished policy does not complete a local turn either, so the run clears its row and fires no notification, where the removed done status event used to reach observeAgentHookCompletionForNotification through the independent agent-status channel.

Technical details
# Notification lost when the side-effect channel has no subscriber

## Affected sites
- `src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.ts:266` — `announceEndedAgentRuns` only runs from `dispatchTerminalSideEffectBatch`, which only runs once the channel is subscribed.
- `src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.ts:291-302` / `:319-322` — `ensureSideEffectChannelSubscription` is invoked only from `registerTerminalSideEffectFactConsumer`.
- `src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts:116-122` — mounted pane skips registration when `!mainSideEffectAuthority && !remoteOutputPaused`.
- `src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.ts:198-251` — parked watcher sets `factSideEffectAuthority = mainSideEffectAuthority || remoteRuntimePty`, false for a local pane under the kill switch.
- `src/renderer/src/components/terminal-pane/parked-terminal-command-status.ts:182-198` — parked `onCommandFinished` drops the row only for SSH PTYs; it never schedules a local completion.
- `src/main/runtime/orca-runtime-create-terminal-side-effect-command-code-detector.ts:88` — the removed `done` went out over `emitTerminalAgentStatusEvents`, which is not gated on the fact channel.

## Required outcome
- An `opencode run` exit still delivers exactly one completion notification with the kill switch off, including when the pane is hidden/parked.

## Suggested approach (optional)
- Subscribe the announcement path unconditionally (e.g. a single channel listener owned outside the per-consumer registration), or keep a status-channel carrier for the run's end alongside the fact.

## Open questions for the human (optional)
- Is `terminalMainSideEffectAuthority: false` a configuration you still support for notifications, or is the intended answer that the kill-switch-off renderer owns this via byte parsing? The parked local path does not, so as written it is a silent loss.

// Why: while this reads `working`, prompt-send verification takes any output as delivery.
this.recordAgentPromptLifecycleState(ptyId, null)
this.reconcileAgentStatusForEndedProcessFn?.(this.collectAgentStatusPaneKeysForPty(ptyId), {
preserveResumeIdentity: true

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.

preserveResumeIdentity: true does nothing whenever the pane also carries launch authority. In the runtime, retirePtyAgentLaunchAuthority runs immediately after clearEndedRun; when pty.launchToken, a restored restoredOrchestrationAuthorityByPtyId receipt, or pty.launchAgent is set it calls reconcileEndedProcessForPaneKeys -> retirePaneAuthority, whose clearPaneCacheState deletes every row for the pane, including the providerSessionOnly remnant this option just re-admitted. The comment and the runtime test claim the OpenCode 1 run keeps its resume identity, but only the no-launch-authority case is tested.

Technical details
# Retained provider-session remnant is deleted by the following retirement

## Affected sites
- `src/main/runtime/orca-runtime-stop-requested-pty-ids.ts:190-196` — `clearEndedRun` retains the remnant via `preserveResumeIdentity: true`.
- `src/main/runtime/orca-runtime-get-unpersisted-tracked-title-for-pty.ts:150-153` and `orca-runtime-emit-daemon-pty-transient-fact.ts:20-27` — `retirePtyAgentLaunchAuthority` runs right after.
- `src/main/runtime/orca-runtime-get-orchestration-dispatch-authority.ts:128-147` — retirement proceeds when `launchToken` / receipt / `launchAgent` is present.
- `src/main/agent-hooks/server/server-authority-fences.ts:51-68` — `retirePaneAuthority` deletes the row via `clearPaneCacheState` and commits the removal.
- `src/main/runtime/opencode-run-lifetime-runtime.test.ts:278-300` — asserts the remnant is kept but never sets `restoredOrchestrationAuthorityByPtyId`; `:232-254` sets it but has no provider session. The combination is untested.

## Required outcome
- Either the remnant survives for a pane with launch authority, or the comment/tests are corrected to say resume identity is only kept when no authority is present.

## Open questions for the human (optional)
- Is dropping the remnant for an authority pane intended (retirement fences the pane and owns resume elsewhere), or should retirement skip the just-retained remnant?

@brennanb2025
brennanb2025 marked this pull request as draft October 1, 2026 23:09
@brennanb2025

Copy link
Copy Markdown
Contributor Author

On hold, back to draft: Orca should not special-case one agent's one-shot command. Status should come only from the agent's own hooks, plus the normal rule that clears a row when the process exits. The opencode run detector this PR refines is being removed instead, in a PR that will be linked here. This PR will then be closed as superseded.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing: Orca should not special-case one agent's one-shot command, so the opencode run detector this PR refines is being removed instead (follow-up PR from branch brennanb2025/opencode-run-remove-detector). After that, opencode run shows no status, the same as other commands that fire no agent hooks.

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