Skip to content

fix(core): avoid holding models.dev refresh lock across network timeout #61118

Closed as not planned

Description

@Bearmancer

Description

When OpenCode boots, the background ModelsDev.refresh() loop in packages/core/src/models-dev.ts acquires lock.withPermit before awaiting fetchApi(), which has an Effect.timeout("10 seconds").

If models.dev is slow, firewalled, or unreachable, lock is held for up to 10 seconds. Meanwhile, foreground initialization calls ModelsDev.get(), which executes populate() and attempts to acquire lock.withPermit(). Consequently, foreground provider and agent initialization blocks for up to 10 seconds waiting on the lock, even when a valid cache or bundled snapshot already exists on disk. This causes the server to return HTTP 503 on /api/model, /api/agent, and /api/provider, stalling client startup.

fetchApi() only needs to run before taking the lock; lock.withPermit is only needed when decoding and writing to the cache.

Plugins

none

OpenCode version

2.2.5 (dev)

Steps to reproduce

  1. Run OpenCode in an environment with high latency or a firewall blocking outbound requests to models.dev.
  2. Start OpenCode (opencode).
  3. Notice /api/model and /api/agent return HTTP 503 for up to 10 seconds while the log records:
    message="Failed to fetch models.dev" cause="Cause([Fail(TimeoutError: Operation timed out after '10s')])"

Screenshot

Not applicable (server lifecycle and background network lock timing issue).

Operating System

Windows 11, macOS, Linux

Terminal

Windows Terminal

Activity

  1. opencodejr commented on Nov 2, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report! I tried this on 2.2.5 (Linux, opencode serve) and couldn't reproduce it.

    To stand in for a firewalled or unreachable models.dev, I pointed OPENCODE_MODELS_URL at a local server that accepts connections and never responds. The server logged the same error you quoted, Failed to fetch models.dev ... TimeoutError: Operation timed out after '10s', so the background refresh did hold its lock for the full 10 seconds. Even so, /api/model, /api/agent and /api/provider returned 200 as soon as the server was up (about 0.5s for the first /api/model request), and every later request took a few milliseconds, including during the hang. I polled every 0.5s for 14s, once with an empty cache and once with a warm one, and saw no 503s.

    This matches the code. ModelsDev.get() → populate() returns the cached catalog, or else the bundled snapshot, before it reaches lock.withPermit. It only waits on the lock when there is neither a cache nor a snapshot, and the snapshot is always bundled in normal builds. So a slow refresh() shouldn't block foreground initialization.

    If you still see 503s, these would help:

  2. Bearmancer commented on Nov 2, 2026

    @Bearmancer
    Author

    Cross-referencing implementation and test evaluation in PR #61120, where the refresh flow and error handling have been exercised against simulated timeouts.

  3. opencodejr commented on Nov 5, 2026

    @opencodejr
    Contributor

    Closing this for now: we couldn't reproduce it on 2.2.5, and we haven't heard back with the details we asked for.

    We pointed models.dev at a server that never responds. The background refresh did time out after 10s, but /api/model, /api/agent and /api/provider returned 200 right away and never 503. That matches the code: ModelsDev.get() returns the cache or the bundled snapshot before it waits on the refresh lock.

    If you still see the 503s, please reply with a startup log (--print-logs --log-level debug), the body of one of the 503 responses, and how you run OpenCode (background service, opencode serve, or from source), and we'll take another look. Related startup-readiness reports: #36117, #60854. Thanks for the report!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions