Skip to content

fix(core): classify models.dev refresh failures - #56752

Open
shreyanshj10 wants to merge 2 commits into
anomalyco:devfrom
shreyanshj10:models-dev-failure-diagnostics
Open

shreyanshj10 wants to merge 2 commits into
anomalyco:devfrom
shreyanshj10:models-dev-failure-diagnostics

Conversation

@shreyanshj10

@shreyanshj10 shreyanshj10 commented Oct 18, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #56742

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The periodic models.dev refresh logged the raw cause and nothing else, so a transport failure and an EACCES on the state directory read identically, and neither said what to try or whether the session was affected:

message="Failed to fetch models.dev" cause="Cause([Fail(HttpClientError: Transport error (GET https://models.opencode.ai/api.json) (cause: Error: Unable to connect...))])"

It now classifies the failure and logs the class, the endpoint, the remedy, the fallback and the retry interval:

message="Failed to refresh models.dev; the bundled catalog is still in use, retrying in 5m" reason=transport detail="cannot reach https://models.opencode.ai/api.json (ConnectionRefused)" next="check this machine's access to the host, and HTTP_PROXY/HTTPS_PROXY if it needs a proxy" errors=[{"type":"TransportError",…},{"type":"Error","code":"ConnectionRefused",…}]

A permission failure instead reads reason=filesystem detail="EACCES: permission denied, mkdir '/home/user/.local/state/opencode/locks'" next="check ownership and permissions on the opencode state and data directories".

Three things worth calling out:

  • The repeat is what made it 56 lines rather than one: the refresh runs on a 5 minute TTL and an unreachable host stays unreachable. An identical failure now logs at Debug, so the default Info threshold shows one line per distinct failure, and a success clears the key so a failure that comes back is reported again.
  • I classified on the HttpClientError reason tag plus ErrorSummary.from instead of pulling networkFailure() out of aisdk.ts. networkFailure cannot see the network error at this site — an HttpClientError carries it on reason.cause, not on its own cause — and its message allowlist does not contain the shape in the report, while ErrorSummary.from is already how this repo puts an error chain in a log and reports whatever code it finds. Glad to switch to the shared classifier if you would rather have it; it is a small change either way.
  • The EACCES half arrives as a defect, and Effect.ignore does not catch defects, so it escaped refresh and took the periodic refresh fiber with it after one line. I recover from it the way writeCache just above does, with catchCauseIf on anything that is not interrupt-only. That is also what makes the retry in the message true for that class.

How did you verify your code works?

Two tests in packages/core/test/models.test.ts, one per class, asserting the level and the fields through a captured logger. Both fail on dev: the transport one captures no classified line at all, and the filesystem one never reaches its assertions because the defect escapes refresh.

From packages/core: bun run test test/models.test.ts (18 pass) and bun typecheck. Also catalog, model, model-resolver and plugin/models-dev (95 pass), and bun run check at the root.

Screenshots / recordings

N/A, log output only.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #41360

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. On the base code, a transport failure produced only the raw Cause(...) line, and an EACCES defect escaped refresh and stopped the periodic refresh loop. With this change both are classified, an identical repeat drops to Debug, and the loop keeps running. I checked this by running the two new tests against the base source (both fail) and with the change (18/18 pass); bun typecheck in packages/core is clean. I couldn't do an end-to-end CLI run here because the local server wouldn't start in this environment, on the base and on this branch alike.

Public API: unchanged. Only a log message and its fields change.

One thing worth fixing: the message always says the bundled catalog is still in use, which is usually not true (see inline).

Related: #41360

Comment thread packages/core/src/models-dev.ts Outdated
const log = failureKey === lastFailure ? Effect.logDebug : Effect.logError
lastFailure = failureKey
return log(
`Failed to refresh models.dev; the bundled catalog is still in use, retrying in ${Duration.format(ttl)}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the bundled catalog is still in use" is often not accurate. After any successful fetch, populate serves the KV-cached catalog (or options.file when set), not the bundled snapshot, and with snapshot: false there may be no bundled catalog at all. Your own transport test starts from a cached catalog. Something neutral like "the current catalog is still in use" would stay true in every case.

This branch has not been deployed

No deployments
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.

core: "Failed to fetch models.dev" reports no cause and no next step

1 participant