Skip to content

Drop input that belongs to a run that already ended - #1164

Merged
bmesuere merged 1 commit into
mainfrom
fix/input-after-run-ended
Oct 1, 2026
Merged

bmesuere merged 1 commit into
mainfrom
fix/input-after-run-ended

Conversation

@bmesuere

@bmesuere bmesuere commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

This pull request stops Papyros from sending input to a run that has already ended, which rejected unhandled with Error: No active call to send a message to.

Sentry: DODONA-FRONTEND-2ER (4 users, Papyros 4.13.3). Two of the three breadcrumb trails start the debugger, close it and start it again within seconds; the third submits input as a run ends.

Cause. InputOutput.provideInput calls Runner.provideInput without awaiting it, and Runner.provideInput forwards to SyncClient.writeMessage, which throws when the client is idle or when the call ends while the write is queued behind the worker's reading status. Input events reach the main thread on the event port and the end of the call on the worker's own port, so the two are not ordered: an input request can arrive after stop() closed the prompt (an interrupted run sends no end event to close it again), or after the next start() reset the input state. In batch mode such a request sends the next buffered line straight away.

  • Runner.provideInput returns without writing when the SyncClient has no active call, and ignores the error when the call ends while the write is queued. Any other write error still rejects. The check uses the client's state rather than RunState, since RunState also tracks package loading and is set by the same unordered events.
  • InputOutput accepts input requests only between the worker's RunCode start event and the end of the run (onRunEnd, which the end event, stop(), failed runs and runtime recovery all call). The worker sends its start event before any input request of that run on the same port, the same ordering Debugger relies on for stale frames. Outside that window an input request opens no prompt, sends no buffered line, and no longer moves the runner to AwaitingInput.

Manual testing instructions
The failure depends on message timing between two ports, so it does not reproduce on demand; the tests below reproduce both paths deterministically.

  • Regression check: run print(input()) in interactive and in batch mode, with and without the debugger, and in batch mode with several buffered lines. Input is read as before.
  • Start a program that reads input, press Stop while the prompt is open, then start the debugger and close it a few times in quick succession: no unhandled rejection in the console.

Checklist

  • Tests: two cases in InputOutput.test.ts, both fail before this change: input submitted after a stopped run resolves without writing and leaves the runner Ready (before: the Sentry error as an unhandled rejection); an input request published after the run ended sends no buffered line and leaves the runner Ready.
  • AI: Claude Code investigated this during Dodona's weekly Sentry triage and wrote the fix.

Reaches production once Dodona bumps @dodona/papyros.

Runner.provideInput forwarded every input to SyncClient.writeMessage,
which throws "No active call to send a message to." once the call has
ended. Nothing awaits provideInput, so the rejection went unhandled.
It now skips the write when the client has no active call, and ignores
that same error when the call ends while the write waits for the worker
to start reading.

Input requests reach the main thread on the event port and the end of
the call on the worker port, so a request can still arrive after a run
was stopped or after the next run began. InputOutput now only accepts
input requests between the worker's start event and the end of the run,
so a stale request no longer opens the prompt, sends a buffered line or
puts the runner in AwaitingInput.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155VQF6bMAnbEaGnc1haWMp
@bmesuere bmesuere added the bug Something isn't working label Sep 25, 2026
@bmesuere bmesuere self-assigned this Sep 25, 2026

@bmesuere bmesuere left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

馃 Reviewed. This addresses a real cross-port race: input can be submitted or delivered after the active call has ended, producing an unhandled rejection and, in batch mode, potentially changing UI state. The SyncClient state guard handles the write race, while the start/end window prevents stale input events from opening a prompt or consuming buffered input. Event ordering on the shared event callback keeps an old run's input before a new run's RunCode event. Focused tests, source lint, and typecheck pass locally. No blocking findings.

@bmesuere
bmesuere marked this pull request as ready for review September 28, 2026 15:41
Copilot AI lite review requested due to automatic review settings September 28, 2026 15:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@bmesuere
bmesuere merged commit 526073a into main Oct 1, 2026
6 checks passed
@bmesuere
bmesuere deleted the fix/input-after-run-ended branch October 1, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants