Skip to content

Comments

fix(session): concurrent prompt race, leaked callbacks, stale busy status#14222

Closed
ArmirKS wants to merge 8 commits intoanomalyco:devfrom
ArmirKS:fix/prompt-race-cancel-busy
Closed

fix(session): concurrent prompt race, leaked callbacks, stale busy status#14222
ArmirKS wants to merge 8 commits intoanomalyco:devfrom
ArmirKS:fix/prompt-race-cancel-busy

Conversation

@ArmirKS
Copy link

@ArmirKS ArmirKS commented Feb 19, 2026

Issue for this PR

Closes #6636
Related: #11699, #6928

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Three fixes in prompt.ts:

  1. concurrent prompt() — no check before createUserMessage(), so two calls on the same session both create user messages. The backward scan picks the wrong one. Added a Set to make the second caller wait.

  2. cancel() leaks callbacks — deletes state without rejecting the promises from line 280-283. They never settle. Now rejects with AbortError first.

  3. stale busy status — session stays busy during post-loop prune() + stream read. Set idle right after the while loop instead.

How did you verify your code works?

Clean build from dev 802ccd37, no plugins. Two prompt_async calls to the same session:

[2026-02-19T01:42:10.370Z] ENTER      session=ses_38c7... agent=undefined model=default
[2026-02-19T01:42:10.448Z] CONCURRENT session=ses_38c7... agent=scout     model=claude-sonnet-4-6
[2026-02-19T01:42:10.451Z] CONCURRENT session=ses_38c7... agent=undefined model=default

Scout's model gets overwritten by the stray's default.

Screenshots / recordings

n/a — not a UI change

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…r messages

Two prompt() calls can hit the same session at the same time. Both
call createUserMessage() and the loop picks the wrong one, so
subagents end up running on the parent model instead of their own.

Added a Set to track active sessions and queue the second caller.

Fixes anomalyco#6636
Relates: anomalyco#11699, anomalyco#6928, anomalyco#8700
cancel() deletes the session state without rejecting promises that
are waiting on callbacks. Those promises never settle so GC can't
collect them. Over a long-running session with frequent cancellations
this adds up.

Now rejects each callback with an AbortError before cleaning up.
@ArmirKS ArmirKS changed the title fix(session): prevent concurrent prompt(), drain cancel callbacks, fix busy status fix(session): concurrent prompt race, leaked callbacks, stale busy status Feb 19, 2026
With the _busy Set guard, concurrent callers never reach the callback
array, so explicitly rejecting each promise is dead code. Just clear
the array defensively.
Removing the early _busy.delete() + idle set before prompt() returns.
The using disposer at prompt() scope exit already handles cleanup.

Deleting _busy before return opened a window where a second caller
could enter the session, create a user message with the wrong model,
and run a loop iteration as the parent instead of the subagent.
@ArmirKS ArmirKS marked this pull request as draft February 19, 2026 16:04
@ArmirKS ArmirKS closed this Feb 19, 2026
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.

Subagent with specific model results in model change in build and plan mode

1 participant