You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
emrg: switching sessions is not refused while a stream runs - #2641
The GUI's emrg:switchSession refused to switch while the session being left still had a
turn streaming: the handler threw stream in progress — cannot switch whenever connManager.get(currentSessionId).ownStream was set. Nothing switched, and the turn kept
running anyway — the refusal cost the navigation and saved nothing.
That check dates from when switching a session closed the old connection: ConnManager.close()
cancels a busy connection before dropping its socket, so letting a switch through would have
killed the running turn. Switching no longer closes anything — it opens or reuses the target
session's own connection and leaves the previous one alone (main.js:625); closing is emrg:closeSession's job. Each session has its own websocket and transcript bucket, so the
guard had nothing left to protect.
What changed
emrg/gui/main.js — the ownStream refusal is gone from the switch handler. The other
refusals on that path (invalid session_id, invalid agent path, the 20-open-session cap) are
untouched, and the G65 lock itself stays: conn.sendTask still marks it (:430) and releases
it on failure (:434), and close()/cancel still use it.
emrg/gui/renderer/src/components/Shell.tsx — selectProjectSession (the sidebar path)
had no catch, so every refusal on this path — the cap, an invalid id — became an unhandled
promise rejection: no visible change at all, and setActiveSid never ran. It now reports the
failure the way the task panel already does and leaves the view where it is. The task panel's
own catch is unchanged.
emrg/gui/test/boot-contract.test.js — one regression test holding both halves: the
switch handler no longer consults ownStream, and its other two refusals still stand.
Agent.md — the GUI totals move 168 → 169, with boot-contract 11 → 12 in the breakdown.
How it was verified
The test is red before the fix and green after — added first, run against the unchanged
handler: # tests 12 / # pass 11 / # fail 1, failing on the new test and nothing else.
Mutant arm: with the refusal put back as a one-line mutant, the same test is the one that
fails (not ok 12), then passes again once removed — so it catches the real thing rather than
a shape near it.
The predicate is pinned in both directions, because the new comment in main.js names the
lock: a comment mentioning ownStream must stay silent, a real read of it must be caught.
Without that arm the assertion would have been red on its own explanatory comment.
cd emrg/gui && npm test — 170 tests, 161 pass, 0 fail, 9 skipped (169 before this change).
cd emrg/gui/renderer && npm run typecheck && npm test — typecheck clean, 740 tests passing.
uv run pytest tests/ -q — 5510 passed, 518 skipped.
uv run --no-sync python3 scripts/check-node-test-count.py — OK, 740 renderer + 169 GUI, both
runners agree; tests/test_doc_counts.py — 82 passed.
CI is pending on this head; nothing here is reported as green.
Superseded by #2640 — folded into it rather than left competing.
Both PRs remove the same refusal from emrg:switchSession, for the same reason: the guard
came from a design in which switching closed the previous connection, and switching no longer
closes anything. #2640 is the better answer to the half this one got wrong. The issue asks that
a refusal be visible where the user acted; this PR answered that by writing the message into
the current session's transcript — the very defect the issue describes for the task panel, where
the user is looking at the task list and never sees it. #2640 puts a role="alert" banner on the
panel the user is standing on, covers both callers, pins the fact that the switch must not close
the connection it leaves, and carries renderer tests for the visibility path.
It also opened first (#2640 at 08:57Z, this one at 09:05Z), so there is no first-come argument for
keeping mine either.
One issue is finished by one PR, and #2637 now reads as a duplicate while both are open. Nothing
here is being re-opened as a third attempt.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2637
The GUI's
emrg:switchSessionrefused to switch while the session being left still had aturn streaming: the handler threw
stream in progress — cannot switchwheneverconnManager.get(currentSessionId).ownStreamwas set. Nothing switched, and the turn keptrunning anyway — the refusal cost the navigation and saved nothing.
That check dates from when switching a session closed the old connection:
ConnManager.close()cancels a busy connection before dropping its socket, so letting a switch through would have
killed the running turn. Switching no longer closes anything — it opens or reuses the target
session's own connection and leaves the previous one alone (
main.js:625); closing isemrg:closeSession's job. Each session has its own websocket and transcript bucket, so theguard had nothing left to protect.
What changed
emrg/gui/main.js— theownStreamrefusal is gone from the switch handler. The otherrefusals on that path (invalid
session_id, invalid agent path, the 20-open-session cap) areuntouched, and the G65 lock itself stays:
conn.sendTaskstill marks it (:430) and releasesit on failure (
:434), andclose()/cancelstill use it.emrg/gui/renderer/src/components/Shell.tsx—selectProjectSession(the sidebar path)had no
catch, so every refusal on this path — the cap, an invalid id — became an unhandledpromise rejection: no visible change at all, and
setActiveSidnever ran. It now reports thefailure the way the task panel already does and leaves the view where it is. The task panel's
own
catchis unchanged.emrg/gui/test/boot-contract.test.js— one regression test holding both halves: theswitch handler no longer consults
ownStream, and its other two refusals still stand.Agent.md— the GUI totals move 168 → 169, withboot-contract11 → 12 in the breakdown.How it was verified
handler:
# tests 12 / # pass 11 / # fail 1, failing on the new test and nothing else.fails (
not ok 12), then passes again once removed — so it catches the real thing rather thana shape near it.
main.jsnames thelock: a comment mentioning
ownStreammust stay silent, a real read of it must be caught.Without that arm the assertion would have been red on its own explanatory comment.
cd emrg/gui && npm test— 170 tests, 161 pass, 0 fail, 9 skipped (169 before this change).cd emrg/gui/renderer && npm run typecheck && npm test— typecheck clean, 740 tests passing.uv run pytest tests/ -q— 5510 passed, 518 skipped.uv run --no-sync python3 scripts/check-node-test-count.py— OK, 740 renderer + 169 GUI, bothrunners agree;
tests/test_doc_counts.py— 82 passed.CI is pending on this head; nothing here is reported as green.