Skip to content

Composer tool loop remains busy after H2 socket closes #33

Description

@tlerbao

Environment

  • OpenCode: 1.18.9
  • Plugin: opencode-cursor-oauth 0.1.1
  • OS: macOS
  • Node.js: 22.19.0
  • Bun: 1.3.14

Summary

Composer can enter a busy state after a multi-turn tool loop when the H2 socket closes. The run does not emit a normal finish event, does not exit the tool loop, and does not return to idle. The same pressure test passes with Grok under the same client and tool setup.

Observed error text:

Cannot connect to API: The socket connection was closed unexpectedly

Sanitized timeline

  • 11:02:12Z: last stream activity
  • 11:02:40Z: H2 socket closed
  • 11:06:28Z: later retry
  • 11:10:48Z: later retry

The timestamps are approximate and intentionally omit local identifiers and request details.

Expected behavior

When the H2 stream closes, the proxy should deterministically emit a terminal error or completion, close the associated stream state, and let the client exit the tool loop. A retry should either establish a clean new stream or fail with a bounded, visible error. The Composer run should never remain indefinitely busy without a finish/exiting-loop/idle transition.

Safe reproduction outline

  1. Configure OpenCode with the Cursor OAuth plugin and a Composer model.
  2. Run a read-only agent task that requires several sequential tool turns, such as inspecting files, searching for a symbol, and returning a final summary.
  3. Allow the task to continue through multiple tool-result round trips.
  4. Observe a stream/socket close during the loop.
  5. Observe that Composer remains busy instead of emitting a terminal event or returning to idle.
  6. Repeat the same multi-round read-only test with Grok; it completes successfully under the same pressure conditions.

No credentials, private payloads, session identifiers, or full prompts are included.

Network observations

At the time of investigation, basic unauthenticated DNS, TCP 443, TLS, HTTPS, and HTTP/2 connectivity to the Cursor host was normal, including repeated HTTP/2 root requests. There were also intermittent historical connection anomalies, including timeout, connection reset, DNS resolution, and TLS handshake errors. This suggests the proxy should handle transient H2 failures explicitly, but the issue is not explained by a persistent basic connectivity outage.

Suspected code areas

  • src/proxy.ts: clean end-stream and onClose branches around lines 1493-1542
  • src/h2-bridge.mjs: stderr and error handling for the child H2 bridge

Please verify that socket-close paths settle pending promises, clear stream/session state, and propagate a terminal event to the OpenAI-compatible response layer.

Validation suggestion

Add a replay or integration test that runs multiple Composer tool/result turns, injects an H2 socket close after stream activity, and asserts that the request reaches exactly one terminal state: completed, failed, or bounded retry. Also assert that no pending tool loop or busy state remains after the close.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions