Skip to content

fix: refresh OpenCode's model catalog before listing its models - #6082

Merged
atomantic merged 1 commit into
mainfrom
cos/task-mtlosgf5/agent-5c11c7e2
Sep 3, 2026
Merged

fix: refresh OpenCode's model catalog before listing its models#6082
atomantic merged 1 commit into
mainfrom
cos/task-mtlosgf5/agent-5c11c7e2

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

The Harnesses page's Refresh models button for OpenCode reported a model list weeks out of date — a model already listed by another machine signed in to the same account never appeared, and the refresh still reported success.

opencode models does not go to the network. It prints from ~/.cache/opencode/models.json, which OpenCode refreshes from a task forked at startup whose failures it swallows — opencode models --refresh prints Models cache refreshed whether or not the fetch worked. Its HTTP client also connects without Happy Eyeballs, so a host advertising an IPv6 default route it cannot actually reach (a VPN interface installing one, with no global v6 address) fails instantly with Unable to connect while curl and Node fetch the same URL fine. The catalog then freezes at whatever day the fetch last succeeded, and every consumer — OpenCode's own TUI included — silently shows the stale list.

server/lib/opencodeCatalogCache.js fetches that catalog from PortOS (Node falls back to IPv4) and atomically writes it where OpenCode reads it, just before the probe runs. That unsticks the vendor's TUI as well as the page.

Because this writes another tool's cache file, it is deliberately narrow. It refuses to fetch or write when:

  • OPENCODE_MODELS_PATH pins a different file, a custom OPENCODE_MODELS_URL moves the cache to a hash-derived name, or OPENCODE_DISABLE_MODELS_FETCH opts out — PortOS cannot be sure which file OpenCode reads, or whether it should read one at all;
  • the file is under five minutes old (OpenCode's own staleness window, so a double-click doesn't pull several MB twice);
  • the body did not parse as a catalog — a stale list beats an empty picker.

Every refusal is best-effort: the probe runs regardless and returns exactly what it would have before.

Test plan

  • server/lib/opencodeCatalogCache.test.js — writes to the path the harness reads; skips a fresh cache without fetching; re-fetches once aged out; keeps the existing catalog on an unreachable endpoint, a non-2xx, a gateway error page, a truncated body, and valid-JSON-that-is-not-a-catalog; neither fetches nor writes under each of the three env refusals.
  • server/services/harnesses.test.js — the refresh primes the catalog for OpenCode and only for OpenCode.
  • Full server suite green (1909 files / 38,517 tests).
  • Verified end to end on a host exhibiting the bug: the previously-missing model appears in opencode models after priming.

https://claude.ai/code/session_01BKLx7uomKwxNJgnzVwbXiU

The Harnesses page's "Refresh models" button for OpenCode reported a model
list weeks out of date — a model already listed on another machine signed in
to the same account never appeared here, and the refresh still reported
success.

`opencode models` does not go to the network: it prints from
`~/.cache/opencode/models.json`, which OpenCode refreshes from a task forked
at startup whose failures it swallows (`opencode models --refresh` prints
"Models cache refreshed" either way). Its HTTP client connects without Happy
Eyeballs, so a host advertising an IPv6 default route it cannot actually
reach — a VPN interface installing one with no global v6 address — fails
instantly with "Unable to connect" and the catalog freezes at whatever day
the fetch last worked.

Fetch that catalog from PortOS (Node falls back to IPv4) and write it where
OpenCode reads it, just before the probe. This unsticks the vendor's own TUI
as well as the page. It refuses to write when `OPENCODE_MODELS_PATH`, a
custom `OPENCODE_MODELS_URL`, or `OPENCODE_DISABLE_MODELS_FETCH` means PortOS
cannot be sure which file OpenCode reads, when the file is under five minutes
old, or when the body did not parse as a catalog — a stale list beats an
empty picker. Every refusal is best-effort: the probe runs regardless.

Claude-Session: https://claude.ai/code/session_01BKLx7uomKwxNJgnzVwbXiU
@atomantic
atomantic merged commit 7edb9bf into main Sep 3, 2026
7 checks passed
@atomantic
atomantic deleted the cos/task-mtlosgf5/agent-5c11c7e2 branch September 3, 2026 15:54
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.

1 participant