Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ breaking changes may land in a minor release.

### Fixed

- Fix the OpenCode V2 SSE-reconnect end-to-end test failing on slow runners (Windows): the
gap's HTTP probe completed the session before the reader reconnected. The fake now
holds the turn busy until the reconnect is up.
- Resolve a codex session's rollout by session id when the hook's transcript path is
missing or unreadable, so its usage is captured and the token budget guard arms
(#775).
Expand Down
20 changes: 17 additions & 3 deletions tests/test_opencode_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@
# html-health the health route answers 200 text/html (the web-UI
# shell a server of another major serves for its routes)
# sse-drop-once completed, but the FIRST event stream closes right after
# server.connected — the turn end arrives on the reconnect
# server.connected, and the turn ends only once the reconnect
# is up (until then an HTTP probe reads busy) — the turn end
# arrives on the reconnect however slowly the client reconnects
# provider-outage (V2) the turn never finishes: the provider is down, so the
# stream carries `session.retry.scheduled` frames (the shape
# captured live from 2.0.27 on a closed port) and nothing else
Expand Down Expand Up @@ -286,6 +288,13 @@ def run_turn():
# let the 204 flush before any scripted death tears the connection down
time.sleep(0.15)
push_turn_start()
if SCENARIO == "sse-drop-once":
deadline = time.time() + 10 # a reader that never reconnects fails the test
while time.time() < deadline:
with LOCK:
if STATE["streams"] >= 2:
break
time.sleep(0.02)
if SCENARIO in ("completed", "sse-drop-once"):
write_result(); write_spec(); finish_turn(); push_idle()
elif SCENARIO == "nudge-then-complete":
Expand Down Expand Up @@ -4466,10 +4475,15 @@ def test_v2_sse_reconnect(tmp_path):

def test_v2_e2e_sse_reconnect_delivers_the_turn_end(tmp_path, v2_fake):
"""End to end: the first event stream breaks right after connecting; the
turn end queued meanwhile arrives on the reconnect and completes the
session."""
turn end arrives on the reconnect and completes the session.

The reconnect is slowed past the gap's per-tick probe cadence, the timing a
slow runner hits anyway: the fake holds the turn busy until the reconnect,
so the probe cannot complete the session over HTTP first.
Ablation: drop the fake's wait for the second stream -> one stream."""
launcher, rec = v2_fake
adapter = make_adapter(tmp_path, binary=str(launcher))
adapter.reconnect_sleep_s = 0.5

result = adapter.run(make_spec(tmp_path, rec, "sse-drop-once"))

Expand Down
Loading