Skip to content

TUI corrupted by auto-fetch of models.dev error log leaks #34730

Closed as not planned

Description

@klx1204

Description

My network environment cannot reach https://models.dev/api.json.opencode periodically auto-fetches this URL on startup and in thebackground. Each failed attempt logs:

  level=ERROR  message="Failed to fetch models.dev"
  cause="HttpClientError: Transport error (GET https://models.dev/api.json)"

These errors leak into the TUI and corrupt the layout behind the textinput area. Since I only use a manually configured model, the registryfetch is unnecessary for my workflow.

I'd like a config option (e.g. "models.dev.auto_fetch": false or"model_registry.fetch": "manual") in opencode.jsonc to:

  1. Disable the periodic auto-fetch of models.dev
  2. Suppress the related ERROR log
  3. Optionally allow a manual fetch via command when needed

Relevant log excerpts

The error appears 189 times in a single opencode.log, spanningacross days and persisting across TUI sessions:

timestamp=2026-06-11T02:55:21.574Z level=ERROR run=e038de2c
message="Failed to fetch models.dev"
cause="Cause([Fail(HttpClientError: Transport error
(GET https://models.dev/api.json) (cause: Error: The socket connection
was closed unexpectedly. For more information, pass `verbose: true`
in the second argument to fetch()))])"

timestamp=2026-06-11T02:59:01.762Z level=ERROR run=44ab922b
message="Failed to fetch models.dev"
cause="Cause([Fail(HttpClientError: Transport error
(GET https://models.dev/api.json) (cause: Error: The socket connection
was closed unexpectedly. ...))])"

timestamp=2026-06-30T11:05:43.193Z level=ERROR run=483673bb
message="Failed to fetch models.dev"
cause="Cause([Fail(HttpClientError: Decode error
(200 GET https://models.dev/api.json) (cause: Error: The socket
connection was closed unexpectedly. ...))])"

timestamp=2026-07-01T01:06:02.239Z level=ERROR run=e3cd7dcb
message="Failed to fetch models.dev"
cause=Cause([Fail(TimeoutError)])

Different error variants observed:

  • Transport error — socket reset (ECONNRESET)
  • Decode error — got HTTP 200 but connection died mid-response
  • TimeoutError — request timed out

The error fires repeatedly within minutes during the same sessionand resumes after restart. This confirms the auto-fetch runsperiodically with no backoff or stop mechanism.

Plugins

@mohak34/opencode-notifier@latest

OpenCode version

1.17.12

Steps to reproduce

  1. Run opencode in a network that cannot reach https://models.dev/api.json
    (e.g. behind corporate proxy, VPN, or air-gapped environment)
  2. Observe the TUI — ERROR messages appear behind the text input area,
    causing UI elements to shift and overlap
  3. Check opencode.log — repeated "Failed to fetch models.dev" entries
    (189 occurrences in a single session in my case)

Screenshot and/or share link

Image

Operating System

Windows (PowerShell 7)

Terminal

Windows Terminal

Activity

  1. changed the title [-]TUI crash caused by auto-fetch of models.dev error log[/-] [+]TUI corrupted by auto-fetch of models.dev error log[/+] on Jul 1, 2026
  2. changed the title [-]TUI corrupted by auto-fetch of models.dev error log[/-] [+]TUI corrupted by auto-fetch of models.dev error log leaks[/+] on Jul 1, 2026
  3. github-actions commented on Jul 1, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  4. soetang commented on Jul 1, 2026

    @soetang

    I am experiencing the same. The TUI corruptions does not appear to happen in 1.17.11 for me. So there seems to be a regression between 1.17.11 and 1.17.12

  5. 0warning0error commented on Jul 2, 2026

    @0warning0error

    there is a workarond:
    set environment variable:

    export OPENCODE_DISABLE_MODELS_FETCH=1
    
  6. klx1204 commented on Jul 2, 2026

    @klx1204
    Author

    I am experiencing the same. The TUI corruptions does not appear to happen in 1.17.11 for me. So there seems to be a regression between 1.17.11 and 1.17.12

    Unfortunately, the issue reoccurs while I'm using 1.17.13

  7. leigoshi commented on Jul 2, 2026

    @leigoshi

    我也遇到了1.17.13

    Image
  8. opencode-agent commented on Jul 3, 2026

    @opencode-agent
    Contributor

    Additional investigation: root cause is an eager ModelsDev refresh logging before the OpenCode logger is installed

    I reproduced the stdout leak locally and traced the likely cause.

    Summary

    ModelsDev.node starts an eager background refresh() during layer construction. When https://models.dev/api.json is unavailable, that refresh logs Failed to fetch models.dev with Effect.logError(...).

    If that log runs before OpenCode's Observability.layer logger has been installed, Effect's default logger writes to stdout. In the TUI, stdout is owned by OpenTUI, so the log text is rendered into the terminal frame and corrupts the UI.

    Providing Observability.layer before building ModelsDev.node prevents the stdout write in the minimal repro.

    Relevant code paths

    • packages/core/src/models-dev.ts
      • refresh() logs the fetch failure:
        Effect.tapCause((cause) => Effect.logError("Failed to fetch models.dev", { cause: cause })),
        Effect.ignore,
      • the service forks an eager refresh during layer construction:
        yield* Effect.forkScoped(refresh().pipe(Effect.repeat(Schedule.spaced("60 minutes")), Effect.ignore))
    • packages/core/src/observability/logging.ts
      • OpenCode's logger writes to the log file by default and to stderr only with OPENCODE_PRINT_LOGS=1.
      • If this logger is not installed yet, Effect's built-in/default logger is used instead.
    • packages/tui/src/app.tsx
      • the TUI renderer uses stdout for the terminal UI, so any accidental stdout log can visually corrupt the frame.

    Minimal repro evidence

    Working tree/tag used for investigation:

    • v1.17.13 / 10c894bdee
    • current origin/dev checked separately at 5455fed78e; packages/core/src/models-dev.ts is effectively unchanged for this behavior.

    First, Effect's default logger writes errors to stdout:

    cd packages/core
    bun -e 'import { Effect } from "effect"; Effect.runSync(Effect.logError("probe-error"));' \
      > /tmp/effect-log.out 2> /tmp/effect-log.err
    wc -c < /tmp/effect-log.out
    wc -c < /tmp/effect-log.err
    cat /tmp/effect-log.out

    Observed:

    stdout bytes: 39
    stderr bytes: 0
    [02:48:03.737] ERROR (#1): probe-error
    

    Then a minimal ModelsDev.node build with a failing mock HTTP client reproduces the same stdout leak:

    import { Effect, Layer } from "effect"
    import { HttpClient, HttpClientResponse } from "effect/unstable/http"
    import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
    import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
    import { Flag } from "@opencode-ai/core/flag/flag"
    import { ModelsDev } from "@opencode-ai/core/models-dev"
    
    await Effect.runPromise(Effect.gen(function* () {
      Flag.OPENCODE_MODELS_URL = "https://offline.invalid"
      Flag.OPENCODE_MODELS_PATH = undefined
      Flag.OPENCODE_DISABLE_MODELS_FETCH = false
    
      const client = HttpClient.make((request) =>
        Effect.succeed(HttpClientResponse.fromWeb(request, Response.error())),
      )
    
      const layer = Layer.fresh(AppNodeBuilder.build(ModelsDev.node, [
        [LayerNodePlatform.httpClient, Layer.succeed(HttpClient.HttpClient, client)],
      ]))
    
      yield* Layer.build(layer)
      yield* Effect.sleep("100 millis")
    }).pipe(Effect.scoped))

    Observed when redirecting streams:

    models stdout bytes: 135
    models stderr bytes: 0
    [02:48:04.307] ERROR (#14): Failed to fetch models.dev {
      cause: {
        _id: "Cause",
        failures: [
          [Object ...]
        ],
      },
    }
    

    Control case: if the same layer is built with Observability.layer provided first, the repro produces no stdout/stderr leak:

    yield* Layer.build(modelsLayer.pipe(Layer.provideMerge(Observability.layer)))
    yield* Effect.sleep("100 millis")

    Observed:

    observed stdout bytes: 0
    observed stderr bytes: 0
    

    Why this matches the user symptom

    When models.dev is reachable, the eager refresh succeeds and no log leaks. In networks where models.dev is blocked, reset, or times out, the eager refresh hits the log path during startup/background refresh. If that happens before the OpenCode logger is active, the log goes to stdout and OpenTUI renders it into the UI.

    This also explains why the app can otherwise keep working: the models catalog has disk/bundled fallback behavior, but the best-effort refresh error is still emitted as an ERROR log.

    Expected behavior

    A best-effort models.dev refresh failure should never write to stdout while the TUI is active. It should either:

    1. be logged only through OpenCode's observability logger (file by default, stderr only when explicitly requested),
    2. be downgraded/suppressed for the eager background refresh path, or
    3. be delayed until after app/TUI logging bootstrap is complete.

    Possible fixes

    • Ensure Observability.layer is installed before any eager-forking service layers can log.
    • Move the eager ModelsDev.refresh() fork out of ModelsDev.node layer construction and start it after runtime/logging bootstrap.
    • Split startup/background refresh from manual opencode models --refresh: background failures can be debug/warn/file-only, while explicit manual refresh can surface failure to the user.
    • As a defense-in-depth rule, avoid stdout logging from any TUI-owned process unless it is intentional UI output.

    Related issues

    Environment used for investigation

  9. kitlangton commented on Jul 3, 2026

    @kitlangton
    Contributor

    The root cause here was a layer-ordering regression in the server route composition: the service graph (including the models.dev background refresh) was built before the logging layer, so its forked refresh fiber kept Effect's default stdout logger. The TUI runs that server in a worker thread of the same process, and worker-thread console output bypasses the TUI's console capture, which is why the error text landed on the screen.

    #35171 fixes the ordering so the refresh failure is written to the log file instead of stdout, and it will be in the next release. Once you're on that version, the UI corruption should be gone. Note the periodic Failed to fetch models.dev entries will still appear in opencode.log when models.dev is unreachable (the refresh is best-effort and falls back to the cached/bundled catalog), and you can disable the auto-fetch entirely with OPENCODE_DISABLE_MODELS_FETCH=1.

    If you still see UI corruption after updating, please comment and we'll reopen.

  10. reopened this on Jul 7, 2026
  11. nexxeln commented on Jul 7, 2026

    @nexxeln
    Contributor

    still see it @kitlangton

  12. AndyLuoJJ commented on Jul 12, 2026

    @AndyLuoJJ

    Still exists in OpenCode version 1.17.18

  13. github-actions commented on Sep 11, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

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