Skip to content

fix(busy-state): release attachManager on /abort + periodic reconciliation#122

Closed
avfirsov wants to merge 1 commit into
grinev:mainfrom
avfirsov:fix/abort-busy-state-reset
Closed

fix(busy-state): release attachManager on /abort + periodic reconciliation#122
avfirsov wants to merge 1 commit into
grinev:mainfrom
avfirsov:fix/abort-busy-state-reset

Conversation

@avfirsov
Copy link
Copy Markdown
Contributor

Problem

Two independent busy trackers — attachManager and foregroundSessionState — were being released asymmetrically. session.idle and /detach both call markAttachedSessionIdle + clearPromptResponseMode, but /abort cleared only foregroundSessionState. Since busy-guard ORs both trackers, the bot would then wedge on the next prompt with «agent is busy» until a process restart.

Pattern table

Call session.idle /detach /abort (before)
markAttachedSessionIdle()
clearPromptResponseMode()
foregroundSessionState.markIdle()
assistantRunState.clearRun()

Changes

src/bot/commands/abort.ts — call markAttachedSessionIdle + clearPromptResponseMode:

  • in the success path (after the server confirms idle), matching the pattern from session.idle and /detach;
  • as a safety net in the error / abortError / AbortError (timeout) paths — even when the server does not confirm the abort, a transient network failure should not permanently wedge the bot.

src/bot/index.ts — add a 15-second busyReconciliationTimer. reconcileBusyState() already exists and is correct (it polls session.status() and resets stale busy), but it was only invoked from the server.heartbeat SSE event. If the SSE stream silently dies, reconciliation dies with it. The timer runs independently of SSE and naturally respects the function's own RECONCILE_MIN_INTERVAL_MS=10s throttle.

Test plan

  • npm run build clean (verified locally)
  • Start bot → send prompt → /abort → send next prompt → should accept (not «agent busy»)
  • Start bot → send prompt mid-stream → kill opencode serve → restart it → bot should recover via the new timer without manual intervention

…ation

Two independent busy trackers (attachManager and foregroundSessionState)
were being released asymmetrically: session.idle and /detach both call
markAttachedSessionIdle + clearPromptResponseMode, but /abort cleared
only foregroundSessionState. The bot would then wedge with
"agent is busy" forever because the busy-guard ORs both trackers.

Changes:
- abort.ts: call markAttachedSessionIdle + clearPromptResponseMode in
  the success path (after server-confirmed idle) and as a safety net in
  the error/timeout paths, so a transient failure cannot permanently
  wedge the bot.
- index.ts: add a 15s busyReconciliationTimer that calls reconcileBusyState
  independently of the SSE stream. Previously reconciliation only fired
  on server.heartbeat SSE events, so a dead stream killed reconciliation
  too. The function has its own 10s throttle, so the 15s tick respects it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@grinev
Copy link
Copy Markdown
Owner

grinev commented May 16, 2026

I think timer is not a geood idea there, I prepaired another fix e80cbcf
Thank you for highlighting the problem

@grinev grinev closed this May 16, 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.

2 participants