anomalyco / anomalyco/opencode

web: config-declared models are hidden by default — released:0 is coerced to "1970-01-01" and fails the client visibility window

Open
#49,605 2 comments 0 reactions 1 assignee View on GitHub

@Hona is already working on this.

Since Sep 17, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

Custom, config-declared models (a provider entry whose models live only in config, with no models.dev catalog entry) are toggled hidden by default in every fresh client of the web/desktop app: the server reports time.released: 0 ("unknown"), the client normalizes that into the valid date "1970-01-01", and the default-visibility rule (visible() in the models context) then treats the model as 56 years old and hides it. Because visibility preferences are persisted only in client-local storage (browser localStorage / desktop storage), nothing on the server remembers the user's "show" toggle — every new browser profile, cleared storage, or another machine starts with these models hidden again.

Environment

  • opencode version: 2.0.6 (opencode serve --service, channel latest; same code path verified on 2.0.4 from the served bundle)
  • OS: Linux x86_64 (server host)
  • Terminal: N/A — defect is in the web/desktop app served by opencode serve
  • Shell: /bin/bash (server host)
  • Install/channel: npm, latest
  • Active plugins: none needed; reproduces with a single custom-provider config entry

Reproduction

  1. Add a custom provider whose models exist only in configuration (no models.dev match):
    {
      "$schema": "https://opencode.ai/config.json",
      "providers": {
        "acme-ai": {
          "name": "Acme AI",
          "package": "@opencode/ai/providers/openai-compatible",
          "settings": { "baseURL": "https://api.example.com/v1" },
          "models": {
            "acme-model-1": { "name": "Acme Model 1" }
          }
        }
      }
    }
    
  2. (Re)start the server. In GET /api/model, acme-ai/acme-model-1 is returned with "enabled": true but "time": { "released": 0 }.
  3. Open the web app in a fresh browser profile (or incognito window).
  4. Settings → Models: "Acme Model 1" is toggled OFF ("hidden") despite there being no stored preference. The model picker does not list it either.
  5. Toggle it ON — it becomes usable in that browser.
  6. Clear site data (or open another browser/machine). The model is hidden again.

Expected Behavior

Models whose release date is unknown should be visible by default — the client fallback even encodes that intent (!date.isValid → visible). Alternatively, configuration should let me set a model's release date so catalog-less models can be dated.

Actual Behavior

Every config-declared model is hidden by default in every fresh client. The only toggle lives in per-browser/per-app storage, so the setting appears to "reset" whenever client state is fresh or lost.

Root cause (verified on served v2.0.6 bundle + live /api/model output)

  1. Server: models without a catalog entry are emitted with "time": { "released": 0 } (epoch 0 = unknown).
  2. Client normalization coerces that epoch-0 into a real date string (in the served runtime chunk):
    release_date: new Date(e.time.released).toISOString().slice(0, 10)  // -> "1970-01-01"
    
  3. Default visibility rule (models context), minimally de-minified:
    visible(model) = stored !== "hide" &&
                     (stored === "show" || latestSet.has(key) || !releaseDate.isValid)
    
    where latestSet = models whose parsed release date is within the last 6 months. "1970-01-01" parses as a valid Luxon date, so the !isValid escape never fires, and the 6-month window fails → hidden.
  4. Storage: visible overrides are kept via Persist.global("model", ...) targeting client-local storage only; none of it is posted back to the server. Selecting a model also writes setVisibility(key, true), which is why "my main models stay visible in the browser I usually use" while other clients reset.
  5. Config cannot override: the native V2 config model schema offers exactly family, name, api, capabilities, request, variants, cost, disabled, limit — no date field. The V1 schema's models.<id>.release_date exists but is dropped silently by migrateModel() in the V1→V2 compatibility layer.

Suggested fix

Either of:

  • Treat released === 0 as unknown when building the client catalog entry (leave release_date unset/null), so the existing !isValid fallback defaults them to visible; and/or
  • expose a per-model release-date field in config (or preserve V1 release_date through migration).

Separately, persisting visibility preferences per-server (not per-browser) would fix the "loses toggles" half of the experience — tracked by #21310 (closed as not planned).

Additional Context

  • Found while investigating why custom-provider models "disappeared from the picker after every server restart" — the restarts merely coincided with fresh/cleared clients; the defaults do the hiding.
  • Only catalog-declared models are immune: they carry real release dates and (when <6 months old) pass the latestSet window.
  • Possibly related older reports: #6169 (TUI picker missing custom models), #6798 (web UI cannot select configured custom models). This one is a narrower, mechanistically identifiable defect.
  • Related feature requests: #21035, #9203 (per-model hiding support).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.