Skip to content

fix(api): bound run-ui-command waits and always disconnect the listener#45

Open
herikwebb wants to merge 4 commits into
mainfrom
fix/ui-command-response-timeout
Open

fix(api): bound run-ui-command waits and always disconnect the listener#45
herikwebb wants to merge 4 commits into
mainfrom
fix/ui-command-response-timeout

Conversation

@herikwebb

Copy link
Copy Markdown
Owner

Problem

wait_for_run_ui_command_response polled forever with no timeout and, unlike its sibling wait_for_chat_user_input, had no try/finally around the signal disconnect. If the browser tab that must answer a UI command disappears (page reload, closed websocket), the request thread kept spinning at 10 Hz indefinitely with the tool loop wedged, and any exception or task cancellation leaked the callback onto the signal.

Change

  • Timeout (default 1800s via NBI_RUN_UI_COMMAND_RESPONSE_TIMEOUT; <= 0 disables) raising TimeoutError. Generous default because run-cell legitimately blocks for the duration of a long-running cell.
  • Listener disconnected in a finally on every exit path — return, timeout, and task cancellation.
  • 6 tests covering the return, timeout, cancellation, env-default, and disabled-bound paths.

Testing

Full Python suite passes (1298 tests).

wait_for_run_ui_command_response polled forever with no timeout and,
unlike its sibling wait_for_chat_user_input, had no try/finally around
the signal disconnect. If the browser tab that must answer a UI command
disappears (page reload, closed websocket), the request thread kept
spinning at 10 Hz indefinitely with the tool loop wedged, and any
exception or task cancellation leaked the callback onto the signal.

Add a timeout (default 1800s via NBI_RUN_UI_COMMAND_RESPONSE_TIMEOUT,
generous because run-cell legitimately blocks for the duration of a
long-running cell; <= 0 disables) that raises TimeoutError, and
disconnect the listener in a finally block on every exit path.
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Automated PR Review

Model: gpt-5.5
Verdict: CHANGES_REQUESTED

Changed files:

notebook_intelligence/api.py
tests/test_run_ui_command_wait.py

Review result:

Automated PR Review Findings

Medium — notebook_intelligence/api.py

Import-time crash on malformed timeout environment variable

RUN_UI_COMMAND_RESPONSE_TIMEOUT is initialized with:

float(os.getenv("NBI_RUN_UI_COMMAND_RESPONSE_TIMEOUT", "1800"))

If the environment variable is set to an invalid value such as "", "30s", or another non-float string, importing notebook_intelligence.api will raise ValueError and can prevent the backend from starting at all. Since this is runtime configuration, it should be parsed defensively with a fallback/default or a clear configuration error that does not make unrelated imports fail unexpectedly.

Suggested fix: move parsing into a helper that catches ValueError, logs the invalid value, and falls back to 1800, or raise a controlled application configuration error during startup.


Low — notebook_intelligence/api.py

Timeout calculation uses wall-clock time instead of monotonic time

The new timeout logic uses time.time():

started = time.time()
...
if timeout > 0 and time.time() - started > timeout:

Wall-clock time can jump forward or backward due to NTP adjustments, VM suspend/resume behavior, or manual clock changes. That can cause premature timeout or significantly delayed timeout. Timeout/deadline logic should use time.monotonic().

Suggested fix: use time.monotonic() for started and elapsed timeout calculations.

@herikwebb herikwebb added the bug Something isn't working label Jul 5, 2026
Review follow-ups (PR #45):
- NBI_RUN_UI_COMMAND_RESPONSE_TIMEOUT is read at import time; a
  malformed value ('', '30s') raised ValueError and blocked the backend
  from starting. Parse via _float_env, which warns and falls back to
  the 1800s default.
- Deadline arithmetic moves from time.time() to time.monotonic() so NTP
  adjustments or a VM suspend/resume can't fire the timeout early or
  defer it indefinitely.
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Automated PR Review

Model: gpt-5.5
Verdict: CHANGES_REQUESTED

Changed files:

notebook_intelligence/api.py
tests/test_run_ui_command_wait.py

Review result:

Findings

Medium — notebook_intelligence/api.py

wait_for_run_ui_command_response uses None as both the “no response received yet” sentinel and the possible response payload:

resp = {"result": None}
...
resp["result"] = data["result"]
...
if resp["result"] is not None:
    return resp["result"]

If the frontend returns a valid JSON null / Python None result for a successful void UI command, the wait loop will ignore that response and eventually raise TimeoutError, even though the callback arrived. This can incorrectly fail commands that intentionally have no return value.

Use a separate received flag or a unique sentinel object, e.g. received = False, or initialize with sentinel = object() and compare by identity. Please also add a test covering {"callback_id": ..., "result": None}.

Review follow-up (PR #45): the wait loop used None both as the
'no response yet' sentinel and as a possible payload, so a void UI
command that legitimately responds with result=None (JSON null) was
ignored and the wait ran into the timeout. Use an identity sentinel
object instead and cover the None-result case with a test.
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Automated PR Review

Model: gpt-5.5
Verdict: CHANGES_REQUESTED

Changed files:

notebook_intelligence/api.py
tests/test_run_ui_command_wait.py

Review result:

Findings

  • Severity: Medium — notebook_intelligence/api.py
    • _float_env() accepts non-finite float values such as NaN, inf, and -inf. This can silently defeat the new timeout behavior:
      • NBI_RUN_UI_COMMAND_RESPONSE_TIMEOUT=nan makes timeout > 0 evaluate false, effectively disabling the bound.
      • NBI_RUN_UI_COMMAND_RESPONSE_TIMEOUT=inf makes the timeout practically unreachable.
    • Since this PR’s goal is to prevent unbounded waits when the frontend disappears, the parser should reject non-finite values with math.isfinite() and fall back to the default, while still allowing documented <= 0 finite values to disable the timeout intentionally.

Review follow-up (PR #45): float('nan') fails every 'timeout > 0'
comparison and float('inf') never elapses — either silently disables
the bound this timeout exists to enforce. Reject non-finite values via
math.isfinite and fall back to the default; a finite <= 0 remains the
documented disable switch.
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Automated PR Review

Model: gpt-5.5
Verdict: APPROVE

Changed files:

notebook_intelligence/api.py
tests/test_run_ui_command_wait.py

Review result:

No automated findings were found.

Residual risks: this change introduces a new timeout behavior for UI command round trips, so very long-running frontend commands may now fail unless NBI_RUN_UI_COMMAND_RESPONSE_TIMEOUT is tuned or disabled. Call-site behavior on TimeoutError should still be validated in integration/manual testing.

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.

1 participant