Skip to content

(sidebar): end a dropped session's running state when main drops it (#375) - #402

Merged
devsuitup merged 4 commits into
mainfrom
fix/375-dropped-session-state
Oct 2, 2026
Merged

devsuitup merged 4 commits into
mainfrom
fix/375-dropped-session-state

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

Closes #375

What changed

When main drops a local session (process-exited), the renderer now ends its running state there instead of waiting for the next poll:

  • onProcessExited (public/app.js) removes the id from activePtyIds, drops its activity state through the new dropLocalPtySession (live subagents, then purgeActivityFor; skipped for remote rows), and runs updateRunningIndicators() at once.
  • dropLocalPtySession (public/session-activity.js) is the single drop; the existing pty-gone scan in updateRunningIndicators() uses it too.
  • updateRunningIndicators() calls renderDefaultStatus() whenever the running set changed. Before, the status bar's N running was only redrawn by loadProjects(), so with no transcript write (CLI stuck in an API retry loop) it stayed stale.

Why

The pty-gone purge only runs when the running-set signature changes. A busy/notification signal that lands after a poll already removed the id re-arms cli-busy on a row the gate never revisits. Fix is at the drop path, not a filter on the count in the view. Rationale is in .ai/contexts/session-state.md ("A session main drops").

Testing

New test/dropped-session-state.test.js (the shipped onProcessExited and updateRunningIndicators are cut out of app.js and run against stubs, as in process-exit-status.test.js; the drop helper runs against the real session-activity.js/sidebar.js through dom-setup.js). All 5 behaviours were red before the change (dropLocalPtySession is not a function, running set still holding b, status bar not redrawn), green after.

Mutations, each turning the new file red then restored: remove activePtyIds.delete(sessionId) in the exit handler (2 fail); remove renderDefaultStatus() (2 fail); skip dropLocalPtySession in the exit handler (1 fail); make dropLocalPtySession skip the purge (2 fail).

Existing pins updated for the renamed call: running-indicators.test.js, session-activity.test.js; process-exit-status.test.js gets two stubs. eslint.config.js declares the new global.

Not verified

The issue was seen once and is not reproduced on demand. The mechanism (a late busy signal after the signature-gated purge, plus the status bar only redrawn by loadProjects()) is reasoned from the code, not observed live. Not run against a live Electron instance.

CHANGELOG: one ### Fixed line under Unreleased.

The pty-gone purge ran only when the running-set signature changed, and the
status bar count was only redrawn by loadProjects(). A busy signal landing
after a poll removed the id left the row busy, and the bar kept counting it.

process-exited now removes the id from the running set, drops the activity
state through dropLocalPtySession and refreshes the indicators at once; a
change of the running set redraws the status bar.

Closes #375
@devsuitup

Copy link
Copy Markdown
Owner Author

Reviewing 7b6e52e (adversarial review in progress).

@devsuitup

Copy link
Copy Markdown
Owner Author

Adversarial review at 7b6e52e: no blocker. The cause holds by reading (the pty-gone purge only runs on a running-set signature change, so a late busy re-arms a row nothing revisits; IPC ordering means no busy can follow process-exited). Panel terminals return early; fork re-key drops the old id harmlessly; renderDefaultStatus runs only on a set change. Being folded in: a late process-exited for an id whose new pty already exists would purge the live session (guard on the current pty), and the remote guard reads sessionMap where pty-gone reads the row's data-remote-alias. 52 targeted tests pass, lint 0 errors.

An exit landing while the same id is being reopened may belong to the old
pty, so the drop is skipped until openTerminal answers. The remote guard
reads the sidebar row, like the pty-gone scan, and the dead typeof guard on
clearActiveSubagentsFor is removed.

Refs #375
@devsuitup

Copy link
Copy Markdown
Owner Author

Reviewing ecbeb6c (adversarial review in progress).

@devsuitup

Copy link
Copy Markdown
Owner Author

Re-review at ecbeb6c: changes needed. The opening flag only covers the await of openTerminal: a stale exit arriving after the reply still drops the new pty (and marks it closed), and a genuine fast-failing launch whose exit lands inside the await is never dropped. Being replaced by a per-spawn pty generation carried in the exit event. The remote-guard unification and the dead-guard removal hold.

The opening flag skipped a genuine fast-failing exit and let a stale exit
that arrived after the reply through. Main now numbers every spawn, returns
the number in the open-terminal reply and sends it with process-exited; the
renderer ignores an exit older than the last reply and buffers one that lands
during the await until the reply says which pty it belonged to.

Refs #375
@devsuitup

Copy link
Copy Markdown
Owner Author

Reviewing 54c6ed3 (adversarial review in progress).

@devsuitup

Copy link
Copy Markdown
Owner Author

Re-review at 54c6ed3: no blocker, no major. The per-spawn generation closes both orderings — a stale exit before the reply is buffered then dropped, after the reply dropped by the recorded generation, and a fast-failing new pty (same generation as the reply) is applied; an exit without a generation is always applied. Minors accepted for a follow-up: the three openTerminal callers outside openSession (dialogs.js ×2, app.js restore) record no generation — not a regression, they open fresh ids; ptyGenerations is never pruned (one number per id). Ready to merge once CI is green on 54c6ed3.

# Conflicts:
#	CHANGELOG.md
#	main.js
#	test/running-indicators.test.js
@devsuitup
devsuitup merged commit 496dbe4 into main Oct 2, 2026
10 checks passed
@devsuitup
devsuitup deleted the fix/375-dropped-session-state branch October 2, 2026 10:10
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.

(sidebar): after a session is dropped by main, the status bar and the sidebar dot keep counting it as running

1 participant