Repository navigation
mcp: remote MCP servers that fail once are never retried until service restart #52237
Description
Summary
When a remote (HTTP) MCP server connection fails — e.g. after macOS sleep/wake kills long-lived connections — the background service keeps it in failed state indefinitely. Creating new sessions or restarting the TUI does not help, because MCP state lives in the background service (opencode serve --service), and there is neither a periodic retry nor a manual reconnect command. Only opencode service restart recovers.
Environment
- opencode version: v2.0.20
- OS: Darwin 27.0.0 arm64 (macOS, MacBook Air)
- Terminal: ghostty (TERM=xterm-256color, COLORTERM=truecolor)
- Shell: /bin/zsh
- Install/channel: latest (npm global)
- Active plugins: @omagents/omagents
Reproduction
- Configure a remote MCP server (e.g.
{ "type": "remote", "url": "https://mcp.context7.com/mcp" }) and start the TUI. Verifyopencode mcp listshows it connected. - Put the Mac to sleep for ~30+ minutes (close the lid). macOS runs DarkWake maintenance cycles every few minutes, which freeze user processes and kill/stall TCP connections.
- Wake the Mac.
opencode mcp listnow shows the remote servers asfailed: Request timed out(or ECONNRESET), and their tools are gone from sessions. - Create a new session, or quit and restart the TUI → servers stay
failed. - Run
opencode service restart→ all servers connect within seconds.
Expected Behavior
Failed MCP connections should recover without restarting the background service. Any of these would work:
- periodic reconnect with backoff for servers in
failedstate, - retry on new session creation (per project/directory),
- retry on system wake / network-change events,
- a manual trigger, e.g. an
opencode mcp reconnect [name]CLI subcommand or a TUI action.
Actual Behavior
Once marked failed, a remote server is never retried while the service keeps running. Observed: two remote servers (context7, websearch) failed at 13:05 local time and were still failed ~1 hour later, even though the network had fully recovered — both endpoints answered curl in <5s, and a different project directory initialized at 14:05 connected all 5 MCP servers within 6 seconds.
The failures correlate to the second with macOS DarkWake events from pmset -g log:
| opencode log (UTC) | pmset (local, UTC+8) |
|---|---|
05:05:41 mcp connect failed |
DarkWake 13:05:41 |
05:10:54 mcp connect failed |
DarkWake 13:10:54 |
05:27:07 event stream stalled |
DarkWake 13:27:07 |
| 05:40:18 | DarkWake 13:40:18 |
| 05:45:30 | DarkWake 13:45:30 |
| 05:50:42 | DarkWake 13:50:43 |
| 05:55:55 | DarkWake 13:55:56 |
Additional Context
Typical log entries:
level=WARN message="mcp connect failed" server=context7 status.status=failed status.error="Request timed out" http.span=313211
level=WARN message="mcp http request failed" errors="[{... \"code\":\"ECONNRESET\" ...}]" durationMs=1252864 server=websearch
What seems to happen: an MCP request/reconnect attempt starts during a 10–60s DarkWake window, the machine goes back to sleep mid-request, the socket hangs across several sleep cycles, and ~313s later it fails with Request timed out or ECONNRESET. During sleep this repeats at every DarkWake (each stall triggers a retry that lands in the next DarkWake). After a real wake, the system is stable, so no retry is ever triggered again and the failed state sticks forever.
Local (stdio) MCP servers recover fine; only remote HTTP servers get stuck.
opencode mcp --help currently lists only list / add / auth / logout — there is no way to force a reconnect.
Workaround: opencode service restart (drops all attached TUI clients).
Activity
github-actions commentedon Sep 30, 2026
This issue might be a duplicate of existing issues. Please check:
zhzjm commentedon Oct 3, 2026
Additional data point from Windows 11 / v2.0.21→2.0.22: this is not limited to remote (HTTP) servers — a local stdio MCP that fails during service startup gets stuck in
failedthe same way.After upgrading to 2.0.22 and the service restart that followed, both of my servers came up as failed during that startup's initialization (network was briefly unavailable at that moment):
exa(remote HTTP):failed: getaddrinfo ENOTFOUND mcp.exa.aicontext7(local stdio viacontext7-mcp):failed: Request timed outNetwork was verifiably healthy immediately afterwards (direct curl to both endpoints succeeded), but the state never recovered on its own — the MCP tools disappeared from the Code Mode tool catalog too, so this wasn't just the lister probe issue from #52563.
opencode service restartre-initialized both and they connected immediately, matching this issue's report.Confirmed still reproducible on 2.0.22. I see #52614 ("retry transient MCP connect failures") was merged on 2026-10-02 but is not contained in the v2.0.22 tag (it is 44 commits ahead of it) — so the retry fix should land in the next release, which presumably closes this.
phoriah commentedon Oct 3, 2026
Adding data points from a Windows / opencode 1.18.31 reproduction that differ from the sleep/wake trigger in the original report.
Different trigger — no sleep or network event involved. In my case the remote server's app was simply not running when the connection was established, so the connect attempt was refused outright:
Refusal is immediate (13–21 ms), not a timeout. Once the app was started again, the endpoint answered normally and there were still zero further connect attempts for that directory over the next 3h42m.
Connections appear to be per project directory, which makes this look intermittent and project-specific rather than global. Every log line carries
directory=, and only the directories whose first connect attempt was refused stayed dropped:The impact is that the tools silently vanish from the agent's catalog. The session reported the MCP as unavailable even though it was reachable, with no user-visible notification. A
type: "local"server in the same config was unaffected — its tools stay listed and individual calls fail withRequest timeoutinstead — so the two failure modes differ enough that the remote case is easy to miss.Version note: this is 1.18.31, where the HTTP API's
POST /api/experimental/mcp/{server}/connectisn't reachable from the CLI (opencode mcpoffers onlyadd,list,auth,logout,debug, and there is noopencode api/opencode servicesubcommand), so on this version there's no shell-side recovery at all.A workaround that doesn't drop attached clients. Editing the server entry in the config forces a reconnect, and the second write restores it — each write makes opencode re-resolve
mcp.serversand reconnect every loaded directory ~200 ms later, with no restart and nothing disconnected:This suggests the reconnect path already exists and is fast; it's just only triggered by config changes, not by a server becoming reachable again. If retry/backoff, or a CLI equivalent of the existing
connectroute, would surface that path, this case would self-heal.phoriah commentedon Oct 3, 2026
Follow-up to my comment above: two corrections and a confirmation on a newer version.
Correction — version. I reported this against 1.18.31, which is no longer the build I have. I'm now on the v2.0.22 desktop app and reproduced it there, so the version reference in my comment is stale. The signature is otherwise identical:
Refused again in 32 ms, then no further attempts — a
type: "local"stdio server in the same config connected normally a few ms later. So this is not fixed in 2.0.22 either, consistent with @zhzjm's note that #52614 merged but isn't in this tag.Correction — local stdio. My comment claimed local servers are unaffected and stay listed with per-call timeouts. That was too strong. @zhzjm reproduced this with a local stdio MCP stuck in
failed, and my own run doesn't disprove it either — the stdio server simply connected cleanly while the remote one was refused. My observation only covers the remote case; the local case is @zhzjm's.A caveat for anyone verifying this by hand.
opencode mcp listreported<remote>asconnectedwhile the server was in fact refused, because it reads the already-running background service rather than the config you point it at. To test this you need an isolated server (--standalone) or you will get a false pass — this looks like the same lister-probe issue as #52563.zhzjm commentedon Oct 4, 2026
Confirming the local stdio case from my side as well, on the CLI/npm build of v2.0.22 (Windows):
ConnectionRefusedin 28 ms,http request failed / transport error / connect failedtriple, then nothing.MCP server process exited with code 1→status.status=failed. I then repointed the same entry at a working stdio server and waited 10+ minutes — zero reconnect attempts.A correction to the workaround, at least on this build —
enabled: true → false → truedoes not reconnect. The config watcher clearly fires on each write (watcher stopped/re-subscribe/startedin the log) but no MCP events follow. Measured:enabledtoggle, global configopencode.jsonmcp connectedwithin secondsSo the effective trigger is "the config diff introduces a server name", not "any write" — on the CLI build the manual recovery is delete-and-re-add, not the
disabledround-trip. Either way your core point stands: the connect path exists and is fast, it's just wired far too narrowly. A startup retry/backoff, or exposing the connect route to the CLI, would make this self-heal.Also +1 on the
listfalse-pass, with a worse variant: in a directory whose config defines 4 servers (2 failed, 2 connected),opencode mcp listprintedNo MCP servers configured. And--standaloneis listed inopencode --helpbut rejected:opencode --standalone mcp list→Unrecognized flag: --standalone in command opencode mcp, so that isolation path isn't reachable from the CLI on 2.0.22 either.Rachdevop commentedon Oct 4, 2026
Another data point, Windows 11 / Desktop + CLI v2.0.22, laptop with Modern Standby. It shows a trigger not covered by #52614.
What happens (log timestamps correlate to the second):
location services evictedthenlocation services bootedfor all 3 loaded directoriesConnectivity state in standby: Disconnected, Reason: Adaptive Connected Standbymcp connect failedfor the 4 remote servers:getaddrinfo ENOTFOUNDWhy it happens:
location-activity.tsevicts a location's services after 60 min without session activity. During standby, that TTL elapses for every location. So any brief wake runs the sweep and reconnects every MCP server at once.failed, nothing retries.Why #52614 won't cover this: its retries span about 1.25 s (250 ms, then 1 s). Here the network stays down for minutes to hours.
What the user sees: the next morning, the directory in active use stays
failedindefinitely, because its TTL keeps being refreshed and it is never re-booted. Idle directories recover on their own at the next eviction/boot cycle, once the network is back.mcp.listshows the mismatch: the home directory has 6/6 connected, the workspace 1/6.Possible fixes:
failedremote servers when a session uses the location (prompt or tool catalog access);Recovery that works on Desktop without a service restart:
/mcps, then toggle each server. This callsmcp.connectfor that location.sunmaxim commentedon Oct 10, 2026
Additional data point — v2.0.26, macOS (arm64), 4 remote HTTP servers: failed state persists 25+ minutes, recovery only when a new client connects
Environment:
~/.config/opencode/opencode.json), alltype: "remote"with a staticAuthorization: Bearerheader, no OAuthTrigger: brief network/VPN unavailability while the background service was running.
Timeline (UTC,
role=serverlog; hostnames redacted):Observations:
Request timed out+AbortError), none of the stuck connectionIDs made another attempt for ~25 minutes, even though the endpoints answered new connections in under a second from 18:30 on. All the 18:30mcp connectedlines belong to new runs/clients (new connectionIDs) — i.e. recovery happened on client (re)connect, not on a retry of the stuck connections.crmat 18:25/18:30 shows a connect attempt can land while the remote side is mid-restart; the following attempt succeeded.mcp transport error … "SSE stream disconnected: TypeError: The socket connection was closed unexpectedly"(connection span ~10 min) exactly when the background service restarted; the affected TUI session lost its MCP tool catalog until the service came back.opencode mcp listfrom the project directory printedNo MCP servers configured. Afteropencode service restartthe same command lists all 4 asconnected.Wishlist (same as the issue): retry
failedremote servers on network-availability change / on next session or tool-catalog activity, or a manualopencode mcp reconnect <name>CLI/TUI trigger.yashikaagarwal-creator commentedon Oct 11, 2026
Additional data: this reproduces on opencode v2.0.24, and the failure is DNS-specific
Adding evidence from another affected machine (macOS 27.0.1, Apple M1, opencode v2.0.24,
Darwin 22.6.0 arm64). I think this is the same root cause already described here and in #43444, but with three findings that pin the mechanism down more precisely.1. The failures are DNS resolver rejections, not socket errors
The log shows
TypeError/getaddrinfo ENOTFOUND, notECONNRESETor a timeout:Counts across the whole log — 174 events total, spanning 10 distinct session runs:
mcp.context7.comopencode.aiapi.githubcopilot.commcp.sentry.devmodels.opencode.ai2. It is not limited to remote MCP servers
opencode.ai,models.opencode.aiandmodels.devfail the same way, so OpenCode's own provider-config and model-list fetches degrade too. This is why the problem looks intermittent — every call retries and eventually succeeds, but the service is permanently burning retries:3. Child processes spawned by the service resolve fine — this isolates the fault
Run inside a shell spawned by the failing service (
spawning process command=/bin/zsh, same run id), 15 rounds each across 3 hosts:System resolvers also answer normally during a burst:
So system DNS is healthy and the resolver works in-process for
nodeandbunchildren, yet the long-lived service process cannot resolve. The failure is isolated to the service process's own HTTP transport state.DarkWake correlation (this is the part that seems decisive)
Local time is IST (UTC+05:30). Matching
pmset -g logwake events against opencode log timestamps:00:49:3219:19:32mcp connect failedserver=sentry at19:19:3202:12:0020:42:00mcp connect failedserver=playwright,openviking at20:42:0302:13:0620:43:0620:43:06–20:43:0806:10:1600:40:16ENOTFOUNDevents inside00:40Dark Wake Count in this sleep cycle: 22.Two more bursts land on wake boundaries —
11events at00:52,19+23at00:56/00:57, and38at01:35— with wake/display-wake activity at06:47and07:03IST in the same window.Recovery is always
opencode service restart. Nothing else clears it:dscacheutil -flushcachedid not help, and a brand-new project directory does not help, because DNS state lives in the shared service process rather than the session.What would help
Anything that resets or revalidates the resolver for the service process on wake. The suggestions already in this issue seem right — the notable addition is that it likely needs to cover the provider/model fetch path, not just MCP, since
opencode.aiandmodels.devdegrade through the same resolver.Environment
v2.0.24TERM=xterm-256color(noTERM_PROGRAM)/bin/zshopencode-pty,opencode-auto-resume,@tarquinen/opencode-dcp,opencode-subagent-statusline,@openviking/opencode-plugin.opencode/plugin/or.opencode/plugins/directory)JosXa commentedon Oct 19, 2026
Another data point: offline location boot after sleep (2.0.29)
On laptops that sleep and use a corporate VPN, the V2 background service regularly ends up with failed MCP servers (and plugin-provided state) that never recover.
opencode service restartis the only fix our users have found. We see this across macOS and Windows users on 2.0.2x.The trigger is a location boot while the network is unavailable:
location-activity.ts).ENOTFOUND) or the connection times out.MCP.startServersets the server tofailedand logsmcp connect failed. Nothing schedules another attempt (packages/core/src/mcp/index.ts,startServeraround L427-481 in v2.0.29).connection.onClose, around L404) is also markedfailedand stopped without a reconnect.reconcile(around L537-575) only restarts servers whose config changed (isDeepStrictEqual), so config reloads do not retry a failed server. The only paths that restart one areMCP.connect(L640, the/mcpstoggle),Credential.Event.Switched, and HTTP session expiry.plugin/supervisor.tskeeps an install or load failure asfaileduntil a source, config, or policy change, the 24-hour update check, or a restart.The location in active use is the one that never recovers, because its activity TTL keeps refreshing and it is never evicted and re-booted.
Reproduction (macOS, 2.0.29)
{"mcp": {"internal": {"type": "remote", "url": "https://mcp.internal.example/mcp"}}}./etc/hoststo127.0.0.1:9), then runopencode service restartand open a session in a project./mcpsshowsinternalas failed./mcpsstill showsfailed, and tool calls return "Reconnect it from /mcps"./mcpsor runopencode service restart; it connects at once.Expected
Failed MCP servers (remote and local) retry with backoff while they are failed, and the service retries connections and failed plugin installs when the network changes or the machine wakes.
Suggestions
failed, including timeouts and stdio servers whose command failed with network errors.mcp.connect(name)(or "retry failed") on the pluginMCPDomain, so plugins can trigger recovery after they refresh credentials.JarvisMaxDev commentedon Oct 23, 2026
Another data point: v2.0.32, Linux (WSL2), remote Streamable HTTP server with OAuth
Environment: opencode 2.0.32 (npm,
latest), WSL2 on Windows (kernel 6.18microsoft-standard-WSL2), background service shared by several open directories. The affected server is the remote Figma MCP endpoint (https://mcp.figma.com/mcp, OAuth). No sleep/wake or network change was identified as the trigger.What we see:
failedin some directories andconnectedin others at the same time.mcp connect failedfor this server. Most failures are eitherECONNRESET(median ~15 s after the request started) orThe operation timed out(300–360 s).failed, it stays that way. A manualPOST /api/experimental/mcp/figma/connectfor that directory restores it immediately, and the endpoint answers normally when probed directly at the same time.onCloseinpackages/core/src/mcp/index.tsstill setsfailedand stops the server without scheduling a reconnect, so upgrading alone would not change this. OnlySessionExpiredErrorgoes throughrecover.Separately, the 300–360 s failures are far longer than the 30 s startup timeout. A minimal repro without OpenCode shows that the MCP client SDK does not bound the
notifications/initializedPOST with the connect timeout or signal (2.0.0 and 2.3.1): modelcontextprotocol/typescript-sdk#3078. We could not confirm from logs that this is the request that hangs here, but it would explain why a connection attempt can staypendingfor minutes before failing.enioclimacosalesjunior commentedon Oct 25, 2026
Additional data point — bare-metal Arch Linux (NetworkManager + Wi-Fi laptop), v2.1.2: instant
EAI_AGAINbursts, per-directoryfailedstate, and a working reconnect-at-runtime mitigationEnvironment
pacman)7.2.11-zen1-1-zen), NetworkManager + systemd-resolvedwlan0-class iface); DNS = single on-link resolver from DHCP/RA on the routerWhat happens
Frequency: 163
EAI_AGAINevents across 3 active days (Oct 16: 37, Oct 23: 38, Oct 24: 86). The surviving state is per directory — directories whose connection was markedfailedstay red indefinitely, while fresh runs in other directories connect all servers fine.The failures are instant (
durationMs=1–6 ms) — the resolver answers "fail fast", there is no network timeout involved. During the same windows, child processes from the same service resolve normally (getent hosts,node dns.lookupfor the same hosts: 12/12 and 45/45 OK). This matches phoriah's macOS finding above: the fault isolates to the long-lived service process's own resolver/transport state.Trigger pattern here is the laptop's Wi-Fi layer — NetworkManager connectivity-change bursts (suspend/resume, roam, DHCP renew). fix(core): retry transient MCP connect failures #52614's 1.25 s connect-retry window is very often shorter than the Wi-Fi outage, so the server ends up
failedand nothing schedules another attempt.Manual recovery that works (v2.1.2)
POST /api/experimental/mcp/{server}/connectsupports?location[directory]=..., and it heals one directory at a time. The un-parametrized POST only fixed the config-owner directory; enumerating the canonical dirs fromGET /api/projectand POSTing per directory is what fully recovers every loaded location. This confirms the "connections are per project directory" observation and that the runtime connect route is already sufficient for a self-heal — it's just not triggered by anything.Runtime mitigation built (works, as a userspace workaround — or a feature)
systemd --userwatcher on the service log (tail -F … | grep --line-buffered "mcp connect failed") + a small recover script that (a) lists loaded directories fromGET /api/project, (b) for each directoryGET /api/mcp?location[directory]=…, (c) for every server infailed/disconnectedstate POSTs the tracked connect route. Debounce viaflock+ 10 s window; logs to journald.mcp connect failed(dead URL, ECONNREFUSED) → watcher fired, script POSTed connect for all four configured directories within seconds (loggedreconectado: <server> @ <dir>×4), and after restoring the URL everything returned toconnectedin all directories without touching the service. After fixing host DNS robustness (cache + fallback), no further bursts.What would help here (echoing the thread's wishlist)
failedremote servers with bounded backoff, per directory (a fresh run elsewhere does not unfail this directory's state)?location[directory]connect route asopencode mcp reconnectwould collapse this whole workaround to one command