Skip to content

fix(abort-handling): extend abort window and use timestamp-based guard to prevent UI idle lock#1846

Open
iyoda wants to merge 2 commits intocode-yeongyu:devfrom
iyoda:fix/abort-window-idle-lock
Open

fix(abort-handling): extend abort window and use timestamp-based guard to prevent UI idle lock#1846
iyoda wants to merge 2 commits intocode-yeongyu:devfrom
iyoda:fix/abort-window-idle-lock

Conversation

@iyoda
Copy link
Contributor

@iyoda iyoda commented Feb 14, 2026

Summary

  • Fix prompt not returning after MessageAbortedError followed by rapid session.idle events
  • Both todo-continuation-enforcer and atlas hooks had guards that only blocked the first idle after abort, allowing subsequent idles to trigger continuation injection and lock the UI

Problem

When MessageAbortedError fires, OpenCode emits multiple session.idle events in quick succession (via both session.idle and synthetic idle from session.status). The existing abort guards had two bugs:

  1. todo-continuation-enforcer: Cleared abortDetectedAt on the first idle within the window, so the 2nd+ idle passed through
  2. atlas: Used a boolean lastEventWasAbortError flag that reset to false after the first idle check

Evidence from logs

The following log excerpt from oh-my-opencode.log shows the actual incident at 2026-02-14T20:05:15Z:

[20:05:15.071Z] [todo-continuation-enforcer] Abort detected via session.error
                 {"sessionID":"ses_...FFF","errorName":"MessageAbortedError"}
[20:05:15.074Z] [auto-compact] session.error received
                 {"error":{"name":"MessageAbortedError","data":{"message":"The operation was aborted."}}}
[20:05:15.075Z] [atlas] session.error {"sessionID":"ses_...FFF","isAbort":true}

--- 88ms later, 5 rapid session.idle events arrive within 50ms ---

[20:05:15.163Z] [todo-continuation-enforcer] session.idle  ← idle #1
[20:05:15.177Z] [todo-continuation-enforcer] Skipped: last assistant message was aborted (API fallback)
[20:05:15.177Z] [atlas] session.idle
[20:05:15.178Z] [atlas] Skipped: last agent does not match boulder agent

[20:05:15.178Z] [todo-continuation-enforcer] session.idle  ← idle #2
[20:05:15.190Z] [todo-continuation-enforcer] Skipped: last assistant message was aborted (API fallback)
[20:05:15.190Z] [atlas] session.idle
[20:05:15.191Z] [atlas] Skipped: last agent does not match boulder agent

[20:05:15.191Z] [todo-continuation-enforcer] session.idle  ← idle #3
[20:05:15.200Z] [todo-continuation-enforcer] Skipped: last assistant message was aborted (API fallback)
[20:05:15.200Z] [atlas] session.idle

[20:05:15.202Z] [todo-continuation-enforcer] session.idle  ← idle #4
[20:05:15.211Z] [todo-continuation-enforcer] Skipped: last assistant message was aborted (API fallback)
[20:05:15.212Z] [atlas] session.idle

--- 38 seconds later, another idle arrives ---

[20:05:53.936Z] [todo-continuation-enforcer] session.idle  ← idle #5 (38s after abort)
[20:05:53.946Z] [todo-continuation-enforcer] All todos complete  ← no injection (todos done)
[20:05:53.946Z] [atlas] session.idle

Key observations:

  1. 5 session.idle events fired from a single MessageAbortedError — the first 4 arrived within 50ms of each other at 20:05:15, and a 5th arrived 38 seconds later at 20:05:53
  2. In this incident, todo-continuation-enforcer happened to skip via API fallback (checking last assistant message), and atlas skipped due to agent mismatch — but neither used the event-based abortDetectedAt guard, meaning the guard was already consumed or cleared
  3. If the agent had matched and todos were pending, the 2nd+ idle would have triggered continuation injection because the abort flag was already cleared after the first idle
  4. The 38-second gap between idle docs: improve README structure for better readability #4 and fix(ast-grep): add validation for incomplete function declaration patterns #5 exceeds the old 3s ABORT_WINDOW_MS, so even the event-based guard would not have caught idle fix(ast-grep): add validation for incomplete function declaration patterns #5 — but the new 10s window still would not cover it. This is acceptable because by 38 seconds, real user activity should have occurred (and the idle fix(ast-grep): add validation for incomplete function declaration patterns #5 was correctly handled by "All todos complete")

Changes

todo-continuation-enforcer (constants.ts, idle-event.ts, test)

  • Increase ABORT_WINDOW_MS from 3s → 10s to cover all post-abort idle events
  • Stop clearing abortDetectedAt within the window — flag now persists until window expires or real activity (user message, assistant response, tool execution) clears it
  • Add test: repeated idles within abort window are all suppressed

atlas (types.ts, event-handler.ts)

  • Replace lastEventWasAbortError: boolean with abortDetectedAt: number (timestamp)
  • Use same 10s ABORT_WINDOW_MS time-based guard — repeated idles suppressed throughout window
  • Extract getAbortClearSessionID() helper to consolidate abort-clear logic across event types

Verification

  • bun test src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts — 43 pass, 0 fail
  • bun test src/hooks/atlas/index.test.ts — 36 pass, 0 fail
  • bun run typecheck — clean
  • lsp_diagnostics — no errors on all modified files

iyoda and others added 2 commits February 15, 2026 05:41
…g across repeated idles

- Increase ABORT_WINDOW_MS from 3s to 10s to cover all post-abort idle events
- Stop clearing abortDetectedAt on first idle within window so repeated
  idles remain suppressed until the window expires
- Add test for repeated idle suppression within abort window
- Use ABORT_WINDOW_MS constant in stale-abort test instead of hardcoded value

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Change SessionState.lastEventWasAbortError (boolean) to
  abortDetectedAt (number) for time-based abort detection
- Use 10s ABORT_WINDOW_MS so repeated session.idle events after
  MessageAbortedError are suppressed throughout the window
- Extract getAbortClearSessionID() to consolidate abort-clear logic
  for message.updated, message.part.updated, and tool.execute events

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: All tests thoroughly cover abort window logic; cubic found no issues and changes are safe with proper handling of abort state.

@iyoda
Copy link
Contributor Author

iyoda commented Feb 14, 2026

Related Issues

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

Comments