Skip to content

fix(server): don't silently swallow WebSocket message processing errors - #337

Closed
klioen wants to merge 1 commit into
e2b-dev:mainfrom
klioen:fix/silent-message-processing-errors
Closed

fix(server): don't silently swallow WebSocket message processing errors#337
klioen wants to merge 1 commit into
e2b-dev:mainfrom
klioen:fix/silent-message-processing-errors

Conversation

@klioen

@klioen klioen commented Sep 7, 2026

Copy link
Copy Markdown

Fixes #336

Problem

ContextWebSocket._receive_message() processes every WebSocket message with no per-message error isolation:

async for message in self._ws:
    await self._process_message(json.loads(message))

A single malformed or unexpected message (bad JSON, missing content/header field, unexpected structure) raises inside the loop, which:

  1. Logs only WebSocket received error while receiving messages: <str(e)> — no stack trace, no message content → the real cause is silently swallowed.
  2. Terminates the async for loop → _receive_task exits → all subsequent kernel messages are never processed and the connection is never re-established.
  3. Runs the finally block which marks every in-flight execution as WebSocketError + UnexpectedEndOfExecution ("The connections was lost..."), even though the connection itself is healthy.

Fix

Isolate per-message handling in an inner try/except:

  • Connection-level failures (ConnectionClosedError / WebSocketException) are re-raised so the outer handler still terminates the loop and cancels all ongoing executions — unchanged behavior for real disconnects.
  • Any other per-message exception now:
    • logs the full stack trace plus a preview of the raw message (logger.exception), so the failure is no longer silent,
    • notifies the affected execution with a MessageProcessingError + EndOfExecution so that execution ends with a clear error instead of hanging,
    • lets the receive loop continue processing subsequent messages.

Testing

  • python3 -m py_compile template/server/messaging.py passes.
  • Behavior verified by reasoning over the existing flow: connection errors still re-raise into the outer handler; per-message errors are contained and surfaced to the corresponding execution.

Notes

  • Message preview is truncated to 500 chars to avoid logging huge payloads.
  • EndOfExecution is sent after MessageProcessingError so the client's _wait_for_result loop terminates (it breaks on END_OF_EXECUTION).

A single malformed or unexpected message (e.g. a missing field in a
Jupyter kernel message) currently bubbles out of _process_message, which
terminates the whole receive loop and then marks every ongoing execution
as WebSocketError + UnexpectedEndOfExecution. The original exception is
only logged as a one-line message without stack trace, so the root cause
is effectively swallowed while all in-flight executions get a misleading
'connection lost' error.

Isolate per-message processing so that:
- a per-message failure logs the full stack trace and a preview of the
  raw message instead of being silently dropped,
- the affected execution is notified with a MessageProcessingError +
  EndOfExecution so it doesn't hang waiting for results,
- the receive loop keeps running for subsequent messages,
- only connection-level failures (ConnectionClosedError/WebSocketException)
  still terminate the loop and cancel all ongoing executions.
@klioen
klioen requested a review from mishushakov as a code owner September 7, 2026 12:51
@cla-bot

cla-bot Bot commented Sep 7, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @klioen on file. You can sign our CLA at https://e2b.dev/docs/cla . Once you've signed, post a comment here that says '@cla-bot check'

@klioen

klioen commented Sep 7, 2026

Copy link
Copy Markdown
Author

Closing: this was created as a test. Thanks!

@klioen klioen closed this Sep 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f27836a65

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +448 to +454
parent_msg_id = None
try:
parent_msg_id = json.loads(message).get(
"parent_header", {}
).get("msg_id")
except Exception:
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge End active executions when no parent ID can be recovered

When malformed JSON—or a valid message missing parent_header—is received while an execution is active, this recovery attempt leaves parent_msg_id unset and then continues the receive loop without queuing any terminal marker. If the discarded frame was the execution's final idle status, _wait_for_result() emits keepalives indefinitely; previously the outer finally ended the request with a WebSocket error. Fail the active execution(s), or terminate the receive loop, when the malformed message cannot be associated with a parent.

Useful? React with 👍 / 👎.

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.

Server silently swallows WebSocket message processing errors and kills the receive loop

1 participant