Skip to content

fix: enforce the sync monitor timeout - #234

Merged
jfrench9 merged 1 commit into
mainfrom
bugfix/monitor-timeout-and-stream-mode
Oct 5, 2026
Merged

jfrench9 merged 1 commit into
mainfrom
bugfix/monitor-timeout-and-stream-mode

Conversation

@jfrench9

@jfrench9 jfrench9 commented Oct 5, 2026

Copy link
Copy Markdown
Member

Summary

OperationClient.monitor_operation never applied its timeout. connect() blocks until the stream ends, so the wait could last forever. GraphClient.materialize (default 600 s) and change_tier (600 s, documented to raise TimeoutError) both pass a timeout expecting it to work. This was a follow-up from #233.

Changes

  • Timeout: a timer closes the stream when timeout passes. The read ends and the call raises TimeoutError("Operation … timed out after Ns"), the same message as the async monitor. The timer is cancelled once the call returns, so a run that finishes in time is unaffected.
  • Reconnect: SSEClient no longer reopens the stream when close() lands during a reconnect backoff.
  • Type: MonitorOptions.timeout is now Optional[float], in seconds. This widens the type and removes nothing.

The other #233 follow-up, a mode="stream" operator call failing, turned out not to be a defect. The server only treats mode=sync specially (routers/graphs/operator/execute.py). stream, async and auto all answer 202, which #233 already follows.

Compatibility

Rides a client patch. Behaviour change: materialize() and change_tier() now stop waiting at their timeout (600 s by default) instead of waiting forever. change_tier raises TimeoutError, as its docstring says. materialize returns a failed MaterializationResult. The server-side operation keeps running either way.

Testing

  • New tests:
    • a silent stream is closed at a 0.1 s timeout and raises;
    • a run that finishes before its timeout returns its result;
    • a close during the reconnect backoff doesn't reconnect.
  • just test-all: 650 passed, 17 skipped. ruff, format and basedpyright are clean.

🤖 Generated with Claude Code

monitor_operation's timeout was never applied, because connect() blocks
until the stream ends. A timer now closes the stream at the timeout and
the call raises TimeoutError. A close during a reconnect backoff no
longer reopens the stream.
@jfrench9
jfrench9 merged commit 3565537 into main Oct 5, 2026
1 of 7 checks passed
@jfrench9
jfrench9 deleted the bugfix/monitor-timeout-and-stream-mode branch October 5, 2026 21:37
jfrench9 added a commit that referenced this pull request Oct 5, 2026
…236)

## Summary

Final fixes from the 2.6.0 verification pass. The main one: #234's
`monitor_operation(timeout=…)` raised `TimeoutError` about 30 seconds
late. Its timer closed the stream from another thread, but that does not
wake a socket read that is already blocked. The read only returned when
the stream's own 30-second read timeout ran out, even with the server
sending keepalives. #234's tests replaced the stream with a mock, so
they never touched a real socket.

## Changes

- **`OperationClient.monitor_operation`:** with a `timeout`, the stream
is read on a worker thread, and the caller waits on an event set by a
terminal event or by the stream ending. The timeout now fires on time. A
finished run returns at once, even if the server holds the socket open
after the terminal event. Without a `timeout`, the call reads the stream
on the calling thread as before.
- **GraphQL reads (`GraphQLClient`):** the resolved token now replaces
any credential in the static headers, so exactly one is sent, as the
REST writes already do. Before, a static `X-API-Key` plus a
`token_provider` JWT sent both headers.
- **`create_report`:** `period_start` / `period_end` are annotated `str
| datetime.date`, which is what they already accepted.

## Compatibility

Rides in the next client release with #235. That release is a **minor**
(2.7.0) because #235 adds a facade parameter. This PR changes no
signatures; the annotation change only widens a type.

## Testing

- **New:** `test_monitor_timeout_fires_on_time_over_a_real_socket` runs
a local SSE server that holds the stream open with keepalives.
`timeout=1` now raises in about 1 s; without the fix the same test fails
after 31 s.
- **New:** two `GraphQLClient` tests check that exactly one credential
is sent. Both fail without the fix.
- **`just test-all`:** 654 passed, 17 skipped; ruff, format and
basedpyright are clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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