Skip to content

fix(tui): keep long-pending requests past Bun's fetch deadline - #55149

Merged
thdxr merged 3 commits into
anomalyco:devfrom
Finn763:tui-fetch-deadline
Oct 19, 2026
Merged

thdxr merged 3 commits into
anomalyco:devfrom
Finn763:tui-fetch-deadline

Conversation

@Finn763

@Finn763 Finn763 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #54974

Type of change

  • Bug fix
  • Refactor / code improvement

What does this PR do?

The TUI builds its client with plain OpenCode.make(...), so its requests inherit Bun's default five-minute fetch deadline. A plugin command whose execute stays pending (or a long session.shell call) is accepted by the server, but the client transport drops the request at ~5-6 min — the server logs 499, the UI restores the composer and navigates away, and the executor keeps running.

This PR gives the TUI client timeout: false at both construction sites (the initial client and the service reconnect path), matching what run and mini already do.

Maintainer follow-ups folded in:

  • One shared helper. The same fetch(request, { ...init, timeout: false }) wrapper lived in packages/cli/src/run/run.ts and packages/cli/src/mini.ts, and would have been a third copy here. It now lives once in @opencode/util/no-timeout-fetch (both cli and tui already depend on @opencode/util) and is used by run, mini and the TUI.
  • Shell failures surface. In an existing session, session.shell was fired with no .catch, so a rejected shell command became an unhandled rejection with no feedback. It now uses the same fail(...) toast + composer restore as the session.command path (new sessions still go through newSession.recover).

Scope note: Promise executors still keep running with no cancellation signal; that belongs with the command lifetime/cancellation work (#53913 / #53914).

How did you verify your code works?

  • Original reproduction from the issue (author): on the base the TUI drops the command by t+380s; on this branch it stays through t+400s and completes once released.
  • packages/util/src/no-timeout-fetch.test.ts: the helper preserves method/headers/body and still honours an abort signal.
  • packages/tui/test/prompt-shell-failure.test.tsx: a failing session.shell in an existing session shows "Failed to run shell command" and restores the composer (fails with an unhandled Shell unavailable error without the fix; 20/20 with --rerun-each).
  • packages/cli/test/mini-timeout.test.ts (with BUN_CONFIG_HTTP_IDLE_TIMEOUT=1) still passes through the shared helper.
  • bun run check clean.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fixes the problem. I ran the #54974 reproduction (a plugin /hang command that stays pending, simulated model) from source on the base and on this branch.

  • Base: by t+380s the TUI had dropped the request, returned to the home screen and put /hang back in the composer, while the plugin's execute was still running.
  • This branch: the TUI stayed on the session past t+400s, and the command finished cleanly once released (~380s).

The packages/tui test passes and its typecheck is clean. The wrapper matches the one in packages/cli/src/run/run.ts, and it covers both places the TUI builds its client (startup and reconnect).

Public API: unchanged.

One point: the added test can be dropped (see the inline comment).

Related: #50135

Before

After

@@ -0,0 +1,37 @@
import { expect, test } from "bun:test"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test can be dropped so the change stays minimal. It replaces globalThis.fetch only to check a one-line spread. It doesn't show that the TUI client actually uses the wrapper, or that a request survives past Bun's deadline.

@BrickerP BrickerP 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.

Verdict: would APPROVE (posted as COMMENT — no write access for formal approve)

Mirrors the existing CLI workaround in packages/cli/src/run/run.ts (fetch(request, { ...init, timeout: false } as BunFetchRequestInit) and wires it into both TUI OpenCode.make sites in packages/tui/src/app.tsx (startup options ~L215 and reconnect next ~L229). That is the right place for #54974’s transport drop: Bun’s ~5 min deadline, not a server limit.

Verified

  • Wrapper { ...init, timeout: false } preserves signal / other RequestInit fields; only Bun’s fetch deadline is disabled — same hang-vs-abort tradeoff the CLI already accepted.
  • Both construction paths covered; reconnect cannot fall back to a default-deadline client.
  • Unit test checks timeout: false forwarding with and without init; type cast to BunFetchRequestInit matches run.ts.
  • Scope correctly leaves Promise-executor cancellation to #53913 / #53914.

Non-blocking

  • packages/cli/src/mini.ts createMiniConnection still builds a plain OpenCode.make (no timeout: false) — same shape as #50135; out of scope here but still a stock-client gap.
  • Optional later: share one helper between CLI and TUI instead of duplicating the one-liner.

CI on head 0e6a6ba: check-duplicates / check-standards / check-compliance all success.

@Finn763

Finn763 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in 7822a90: the wrapper test is dropped so the diff stays minimal -- it only pinned the one-line spread. The change is now just packages/tui/src/util/no-timeout-fetch.ts plus the two OpenCode.make sites, and the PR description is updated (your base/branch reproduction is cited there as the verification).

Finn763 and others added 3 commits October 19, 2026 13:17
The TUI client inherits Bun's default five-minute fetch deadline, so a plugin command whose execute stays pending is dropped client-side (server logs 499) while the executor keeps running. The run entry point already opts out for long-lived requests; give the TUI client the same treatment at both construction sites (initial and service reconnect).
The test only pinned a one-line spread and did not show the TUI client actually uses the wrapper; removing it keeps the change minimal.
Move the Bun fetch timeout opt-out into @opencode/util/no-timeout-fetch and use
it from run, mini and the TUI. Existing-session shell commands now toast and
restore the composer on failure, matching the command path.
@thdxr
thdxr force-pushed the tui-fetch-deadline branch from 7822a90 to fc9891f Compare October 19, 2026 17:23
@thdxr
thdxr merged commit 52a10f5 into anomalyco:dev Oct 19, 2026
19 checks passed
@thdxr

thdxr commented Oct 19, 2026

Copy link
Copy Markdown
Member

Thanks @Finn763, this is merged as 52a10f5. Your fix, plus the clear reproduction in the issue, made this easy to land.

Before merging, I rebased it onto the latest dev and added two maintainer follow-ups:

  • One shared helper. The timeout: false fetch wrapper now lives once in @opencode/util/no-timeout-fetch. The TUI, run and mini all use it, so there are no longer three copies. There is a small test showing that it keeps the request init and still honours an abort signal.
  • Shell failures surface. In an existing session, a failed ! shell command used to reject with nothing handling it. It now shows a "Failed to run shell command" toast and puts your text back in the composer, the same way slash commands already did. A TUI test covers this.

Cancelling plugin executors is still out of scope here and stays with #53913 / #53914.

— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

brentyates-swx pushed a commit to swxtchio/swx-opencode-archive that referenced this pull request Nov 4, 2026
…lyco#55149)

Give the TUI client timeout: false, share the no-timeout fetch helper across run, mini and the TUI via @opencode/util/no-timeout-fetch, and surface existing-session shell failures with a toast and composer restore.

Closes anomalyco#54974

Co-authored-by: Finn763 <finn763@users.noreply.github.com>
(cherry picked from commit 52a10f5)
brentyates-swx added a commit to swxtchio/swx-opencode-archive that referenced this pull request Nov 6, 2026
* docs: plan transport and lifecycle backport

Implement the assigned transport and lifecycle behavior in existing fork owners.

- Pick each donor in its own -x commit; omit shell-failure UI and absent CLI/MCP payload.
- Disable Bun’s independent fetch deadline only for long network clients and native provider requests; preserve caller signals, domain deadlines, and the #303 watchdog.
- Track detached POSIX groups only while cleanup is owed, preserving #380 escalation and release of intentional descendants; route terminal SIGHUP through the TUI lifecycle.
- Add real-server and child-process regressions with removal proofs, then record donor coverage and focused checks in CHANGESET.md.

* fix(tui): keep long-pending requests past Bun's fetch deadline (anomalyco#55149)

Give the TUI client timeout: false, share the no-timeout fetch helper across run, mini and the TUI via @opencode/util/no-timeout-fetch, and surface existing-session shell failures with a toast and composer restore.

Closes anomalyco#54974

Co-authored-by: Finn763 <finn763@users.noreply.github.com>
(cherry picked from commit 52a10f5)

* fix(core): disable Bun's fetch idle timeout for provider requests (anomalyco#55007)

(cherry picked from commit 460c626)

* fix(util): kill spawned process groups when opencode exits (anomalyco#59411)

(cherry picked from commit e2bc79d)

* fix(core): type Bun fetch timeout overrides

* test(opencode): verify mini attach timeout wiring

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(core): verify abrupt owner process-group cleanup

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(tui): verify SDK long-call fetch policy

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* fix(tui): preserve nohup SIGHUP behavior

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(core): verify native fetch timeout override

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(core): prove native requests outlive default idle timeout

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(opencode): verify attached runs survive Bun idle timeout

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* docs: record transport and lifecycle backport

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* docs: clarify backport regression ownership

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(opencode): allow Bun 1.4.4 server startup window

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test: shorten idle-timeout acceptance tests

Run the native and attached held-response witnesses in bounded Bun 1.4.4 child processes with BUN_CONFIG_HTTP_IDLE_TIMEOUT=2 so they stay out of the multi-minute range.

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(tui): prove SDK long calls survive Bun idle expiry

Exercise SDKProvider's external SDK and SSE paths with a held event stream, a real timeout control, and a completed event after the injected short idle expiry.

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(opencode): prove mini attach survives Bun idle expiry

Hold the real mini attach session response in a Bun 1.4.4 child process, observe an unmodified control fetch time out first, then release the expected 404 response.

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* test(tui): make SIGHUP fixture reports truthful

Keep the nohup fixture scoped to listener policy and process survival, and add a non-TTY non-nohup default-disposition witness while retaining lifecycle assertions only from the real TUI producer.

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* fix(tui): clean Worker-owned process groups on exit

Acknowledge Worker group ownership and release events, kill and await remaining groups before the shutdown reply, and use the recorded PGID ledger as the bounded TUI-owner backstop when shutdown is unconfirmed.

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* docs: finalize transport exit review coverage

Record the acknowledged TUI Worker group lifecycle, truthful SIGHUP behavior, and short Bun 1.4.4 behavioral witnesses in the backport ledger.

Claude-Session: c368ed23-1e31-4f2c-b305-b84f21d79cc5

* fix(tui): centralize Worker process-group ownership

* docs: plan transport exit review fixes

1. Make worker and parent ownership ledgers converge on each local acquire, release, and cleanup decision even when acknowledgement is rejected or delayed; validate recorded identities before parent-side signalling.
2. Add rejected/delayed acknowledgement regressions for acquire, release, cleanup announcement, and release/reacquire identity fallback.
3. Harden fixture cleanup and deadlines; add Linux-only guards; correct CHANGESET ownership and SIGHUP claims; remove only test hooks no longer needed.
4. Verify focused changed cases and then each importing test file once under checksum-verified Bun 1.4.4; commit without pushing.

* fix(core): reconcile process-group acknowledgement failures

Reconcile Worker and parent ownership ledgers on local acquire, release, and cleanup facts, independent of rejected or delayed acknowledgements. Validate recorded member identities before signalling and preserve released member identities across reacquisition.

Bound identity-owned process fixture cleanup, gate procfs child fixtures to Linux, retain portable SDK cancellation coverage, and correct the #410 SIGHUP and ownership ledger.

Claude-Session: unknown

* test(opencode): gate shell output on spawn readiness

Wait for the process spawn acknowledgement before the ShellTool first-output deadline and reap the child on readiness failure. Tighten process-group fixture types for package typecheck.

Claude-Session: unknown

* fix(core): restore cross-platform process group cleanup

Claude-Session: unavailable (OpenCode session)

* fix(core): unify process-group ownership decisions

Claude-Session: unavailable (OpenCode session)

* fix(core): publish process-group ownership updates

Claude-Session: unavailable (OpenCode session)

* test(opencode): cover cancellation-overlap ownership

Claude-Session: unavailable (OpenCode session)

* test(core): wait for process-group cleanup

Claude-Session: unavailable (OpenCode session)

* test(opencode): harden process-group test results

Claude-Session: unavailable (OpenCode session)

* test(opencode): validate cancellation result shape

Claude-Session: unavailable (OpenCode session)

* test(opencode): type fixture results as unknown

Claude-Session: unavailable (OpenCode session)

---------

Co-authored-by: Finn763 <165816600+Finn763@users.noreply.github.com>
Co-authored-by: Finn763 <finn763@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
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.

TUI/stock client drops long-pending plugin command request at ~5 min (Bun fetch deadline); execute keeps running orphaned

3 participants