You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Instance service init has no timeout and can hang the bootstrap layer indefinitely #46166
Title:
Instance service init has no timeout and can hang the bootstrap layer indefinitely
Description: packages/opencode/src/project/bootstrap.ts calls init() on each per-instance service
(lsp, share, format, vcs, snapshot, project) inside an Effect.forEach with concurrency: "unbounded". Each init() is unbounded; the existing catchCause only
fires on rejection, not on hang; and there is no per-service log line. When a single
service hangs (broken native binding, deadlocked internal state, slow disk that
prevents the share/format services from enumerating, etc.) the bootstrap layer
silently waits, the rest of the services may also stall depending on shared InstanceState, and there is no log line to point at the offending service.
#33905 covered the related plugin-init npm install and was fixed in the npm install
path. The instance service bootstrap itself is a separate layer and still has no
timeout.
Adding evidence from a production-ish reproduction (InsightForge per-project opencode serve containers, Docker Desktop on macOS, arm64, Ubuntu 22.04 sandbox, opencode 1.18.26 + 1.18.27 — the no-timeout bootstrap hang described here is exactly what we hit):
Two stacked bootstrap waits observed, both in the critical path of the first POST /session:
models.opencode.ai (models.dev catalog) fetch, no effective timeout. On our network the TCP connect to 172.66.173.149:443 succeeds and then goes silent for 210s+ (zero payload bytes; pcap + /proc/net/tcp sampled every 3s show ESTABLISHED snapshot 2→70). During the entire hang GET /global/health answers instantly while POST /session blocks — so the un-bounded init hides behind health checks. curl to the same URL from the same container: 200 in 0.29s, so it's specifically the bootstrap client's fetch pattern (~50 parallel TLS connections) getting hung/throttled.
Measured impact on our worst-case network: fresh container → first successful session: 295s before mitigations, ~152s after (blocking the catalog host via docker run --add-host models.opencode.ai:127.0.0.1 removes the dominant hang loss-free — we never use the free catalog). With --pure (no plugins) boot-to-session is ~2s — consistent with the plugin path being the residual blocker.
This matches the OP's structural point exactly: catchCause only fires on rejection, not hang; and unlike this issue's synthetic never-resolving init(), our hang is a real service (the npm installer / models fetch) whose Effect lacks a timeout. The two upstream fixes we'd need:
per-service timeout + warning in InstanceBootstrap.run (this issue), and
Thanks for the production data — this is the missing evidence for the structural point.
#46167 (open, no review yet) implements exactly the first of your two requested fixes:
a per-service Effect.timeout around each service.init() in InstanceBootstrap.run,
with a logWarning naming the service that timed out. The concurrency shape stays unbounded; the other services still complete. That turns your "no log line until the
process is killed" into a named warning within seconds.
It does not address your second point — the models.dev fetch and the plugin dep-install
being in the blocking path (#41360, #44684). Those need to become non-blocking with a
fallback, which is a separate contract.
One observation from our own profiling that may be relevant to the second point: even
when the catalog fetch succeeds and the cached models.json is used, GET /provider
stays expensive. ProviderHttpApi.list caches nothing and rebuilds the whole catalog per
request — 213 providers, ~7500 models, a 5.8 MB response, of which toPublicInfo spends
~143 ms per call running Schema.is over every model (all of them pass; the data comes
from our own catalog). On our desktop start the client requested that up to 27 times,
so ~156 MB of JSON per start with two providers actually connected. Different layer from
this issue, so we will file it separately rather than widen the scope here.
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.
Activity
shrijayan commentedon Sep 4, 2026
Adding evidence from a production-ish reproduction (InsightForge per-project
opencode servecontainers, Docker Desktop on macOS, arm64, Ubuntu 22.04 sandbox, opencode 1.18.26 + 1.18.27 — the no-timeout bootstrap hang described here is exactly what we hit):Two stacked bootstrap waits observed, both in the critical path of the first
POST /session:models.opencode.ai (models.dev catalog) fetch, no effective timeout. On our network the TCP connect to
172.66.173.149:443succeeds and then goes silent for 210s+ (zero payload bytes; pcap +/proc/net/tcpsampled every 3s show ESTABLISHED snapshot 2→70). During the entire hangGET /global/healthanswers instantly whilePOST /sessionblocks — so the un-bounded init hides behind health checks.curlto the same URL from the same container: 200 in 0.29s, so it's specifically the bootstrap client's fetch pattern (~50 parallel TLS connections) getting hung/throttled.The plugin "background dependency install" wait (covered precisely in 1.18.21: plugin installer times out fetching public deps from registry.npmjs.org (NpmInstallFailedError: FetchError) — silent dead plugin / boot hang; headless run/serve hangs with any plugin #44684 — same
NpmInstallFailedError … registry.npmjs.org/@opencode-ai%2fpluginerror on 1.18.26/1.18.27). Notably the trigger is the plugin file's presence, not its imports — a zero-import plugin stalls identically; pre-seedingnode_modulesdoes not bypass the installer;OPENCODE_STRICT_CONFIG_DEPS=falsehas no effect in vivo.Measured impact on our worst-case network: fresh container → first successful session: 295s before mitigations, ~152s after (blocking the catalog host via
docker run --add-host models.opencode.ai:127.0.0.1removes the dominant hang loss-free — we never use the free catalog). With--pure(no plugins) boot-to-session is ~2s — consistent with the plugin path being the residual blocker.This matches the OP's structural point exactly:
catchCauseonly fires on rejection, not hang; and unlike this issue's synthetic never-resolvinginit(), our hang is a real service (the npm installer / models fetch) whose Effect lacks a timeout. The two upstream fixes we'd need:InstanceBootstrap.run(this issue), andFull reproduction + pcap + probe scripts: #47279 (consolidated report; happy to move everything here if maintainers prefer).
CannonRS commentedon Sep 4, 2026
Thanks for the production data — this is the missing evidence for the structural point.
#46167 (open, no review yet) implements exactly the first of your two requested fixes:
a per-service
Effect.timeoutaround eachservice.init()inInstanceBootstrap.run,with a
logWarningnaming the service that timed out. The concurrency shape staysunbounded; the other services still complete. That turns your "no log line until theprocess is killed" into a named warning within seconds.
It does not address your second point — the models.dev fetch and the plugin dep-install
being in the blocking path (#41360, #44684). Those need to become non-blocking with a
fallback, which is a separate contract.
One observation from our own profiling that may be relevant to the second point: even
when the catalog fetch succeeds and the cached
models.jsonis used,GET /providerstays expensive.
ProviderHttpApi.listcaches nothing and rebuilds the whole catalog perrequest — 213 providers, ~7500 models, a 5.8 MB response, of which
toPublicInfospends~143 ms per call running
Schema.isover every model (all of them pass; the data comesfrom our own catalog). On our desktop start the client requested that up to 27 times,
so ~156 MB of JSON per start with two providers actually connected. Different layer from
this issue, so we will file it separately rather than widen the scope here.
Tarquinen commentedon Oct 20, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊