Skip to content

fix(client): reconnect transient stalls instead of replacing the service (#52049) - #52733

Open
JerryLiu369 wants to merge 1 commit into
anomalyco:v2from
JerryLiu369:fix/52049-autofix
Open

JerryLiu369 wants to merge 1 commit into
anomalyco:v2from
JerryLiu369:fix/52049-autofix

Conversation

@JerryLiu369

@JerryLiu369 JerryLiu369 commented Oct 2, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #52049

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

On Windows the v2 managed service (opencode serve --service) was being killed and respawned during normal load, aborting every in-flight session and background subagent. Two client-side defects caused a transient stall to escalate into a full service replacement:

  1. Event-stream idle watchdog margin too small. The server writes a keepalive comment on /api/event every 15 s, but the client aborted the stream after 45 s without a byte (defaultIdleTimeout = 45_000) — only three missed keepalives. Under load (watcher re-registration churn, many cold-booting locations) a 45 s gap is easy to hit, so the client aborted a healthy stream with Event stream stalled.

  2. Service eviction threshold too eager. After the stalled stream, the client only tolerated three consecutive probe timeouts (timeouts.count >= 3) before declaring the service unresponsive and replacing it (Background service is unresponsive; recovery cannot preserve persistent terminals) instead of reconnecting. A few slow probes were enough to tear down a live service and kill its sessions.

This PR widens both margins:

  • defaultIdleTimeout 45 s → 90 s (six missed keepalives), so a transient stall triggers the normal abort-and-reconnect path rather than looking like a dead stream. The doc comment now explains the keepalive-derived budget.
  • Eviction requires five consecutive probe timeouts (was three) before the service is replaced, in both the Effect (packages/client/src/effect/service.ts) and Promise (packages/client/src/promise/service.ts) variants. Transient Windows probe stalls now reconnect; only sustained unresponsiveness evicts.

No public API or type changes; both margins remain overridable via idleTimeout / EnsureTiming.

How did you verify your code works?

  • Added regression coverage: keeps a registered service that recovers after transient probe stalls (both Effect and Promise service suites) asserts a service that stalls on early probes but recovers is kept (same pid/url, process still alive), and updated the eviction test to expect five probes before replacement.
  • Added the default idle timeout spans several server keepalives in solid-connection.test.ts, pinning defaultIdleTimeout >= 6 * 15_000 so the watchdog can never again be tightened below the server's keepalive budget.
  • Extended the service fixture with a flaky mode.

Ran bun test in packages/client:

 177 pass
 4 fail
Ran 181 tests across 16 files.

The 4 failures are pre-existing in this worktree and unrelated to this change (exposes every standard HTTP API group, session methods use the public HTTP contract, session methods retain decoded Effect inputs and outputs, public import boundaries — all fail identically on the unmodified parent commit, caused by a stale symlinked node_modules in the factory worktree; no bun install was run). The 174 baseline passing tests still pass, plus the 3 new tests.

Screenshots / recordings

N/A — client/service logic and unit tests; no UI surface.

Checklist

  • I have read the contributing guidelines
  • I have read the documentation
  • I have run bun test for the affected packages
  • My changes do not introduce new warnings or errors
  • Any AI-assisted work has been reviewed and verified by me

@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@r266-tech

Copy link
Copy Markdown

Could you retarget this to v2? The current V2 contributing guide requires it. GitHub currently reports 7,512 changed files against dev, while commit 018554f7 itself changes only 7 files.

One recovery boundary I checked on unchanged client sources matching current v2 (12810e84): two clients connected to an isolated HTTP/SSE fixture both hit a shortened idle watchdog, then reconnected through actual Service.ensure(). With /api/info responsive, all four subscriptions retained the same registration and PID (1 test passed). Thus stream silence alone already reconnects; eviction additionally requires failed health probes. The timeout changes address that additional failure window, but this fixture does not reproduce the reported desktop/CLI interaction or validate a live-session fix. AI-assisted investigation; no live service or model calls.

@JerryLiu369
JerryLiu369 changed the base branch from dev to v2 October 3, 2026 05:31
@JerryLiu369

Copy link
Copy Markdown
Author

@r266-tech Good catch, and you're right on both counts: the fix was developed against v2 code (packages/client only exists on the v2 line) but the PR was opened against dev, hence the 7512-file diff. Retargeted base to v2 — the diff is now the intended 7 files / 1 commit. Thanks for verifying the recovery boundaries on v2 (12810e8).

This branch has not been deployed

No deployments
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.

cli(win): 45s event-stream idle watchdog restarts the managed service, aborting all sessions/subagents

2 participants