Skip to content

[Bug]: OpenCode inventory silently truncated to a prefix of opencode models --verbose — 79 of 186 models missing #5298

Description

@giov-bs

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Related: #4239. That issue's title/body is about OPENCODE_CONFIG_CONTENT="{}" clobbering user config. This report is about the separate intermittent inventory loss described in its second comment, filed separately so it can be triaged on its own. I can reproduce it deterministically and have narrowed the mechanism.

Area

apps/server

Problem

T3 Code's cached OpenCode inventory contains a contiguous prefix of the models OpenCode actually reports. Everything past the cut is silently dropped, and the provider still reports ready with a healthy "4 upstream providers connected" message.

On my machine 79 of 186 models are missing, including every recent OpenAI model (openai/gpt-5.5, openai/gpt-5.6, openai/gpt-5.6-luna, openai/gpt-5.6-sol, openai/gpt-5.6-terra). Restarting T3 Code restores them until a later refresh drops them again.

Steps to reproduce

  1. Enable only the OpenCode provider (no external server URL configured, so the CLI inventory path at OpenCodeProvider.ts:394 is used).
  2. Use an OpenCode install with a large model inventory (mine reports 186 models across 4 upstream providers).
  3. Let a scheduled provider refresh run.
  4. Compare the cache against the live CLI:
$ opencode models --verbose | grep -cE '^\S+/\S+$'
186

$ jq '.models | length' ~/.t3/caches/opencode.json
107

Expected behavior

The picker offers every model OpenCode reports, or the provider surfaces an error when the inventory could not be fully read.

Actual behavior

107 of 186 models are cached. The loss is a clean positional split, not a random subset.

Per upstream provider:

provider live CLI T3 cache
anthropic 10 10
bsp 3 3
gemini 30 30
openai 143 64
total 186 107

Mapping each cached slug back to its position in opencode models --verbose output order:

  • present: positions 1–107
  • missing: positions 108–186
  • models in the cache that are not in the live list: 0

So the cache is exactly the first 107 entries of the CLI output. openai/gpt-5.6-sol sits at roughly position 114, just past the cut.

The cut is not registry staleness. The dropped tail includes long-standing models that certainly existed when the snapshot was written:

openai/o1
openai/o3
openai/text-embedding-ada-002
openai/tts-1
openai/whisper-1

It is also not the empty-name skip at OpenCodeProvider.ts:230. The dropped models have valid names:

$ opencode models --verbose | grep -A 8 '^openai/gpt-5.6-sol$'
openai/gpt-5.6-sol
{
  "id": "gpt-5.6-sol",
  ...
  "status": "active",
  "name": "GPT-5.6 Sol",

The CLI itself is stable — three consecutive runs each returned 186. The truncation happens in T3 Code's capture of the subprocess stdout. In the current output, position 108 begins at byte 111,805 of 191,445, so roughly the last 40% of the stream is lost.

Why it fails silently

runOpenCodeCommand (opencodeRuntime.ts:397-409) reads stdout with collectStreamAsString, which calls collectUint8StreamText without maxBytes, so the cap defaults to POSITIVE_INFINITY. There is no intentional limit — the stream is ending early, which looks like the stdout pipe not being fully drained before the child's exit resolves. 191 KB across a 64 KB pipe buffer needs several reads.

Three separate behaviors then conspire to hide it:

  1. parseModelsCliOutput swallows the truncated trailing JSON block in a bare catch {} (opencodeRuntime.ts:209-211), so the tell-tale parse failure is discarded.
  2. connected is not reported by OpenCode on this path — it is synthesized as [...providers.keys()] (opencodeRuntime.ts:229), i.e. derived from whatever happened to parse. A truncated read still yields all 4 providers here, because each provider's first models land early in the stream, so the snapshot looks healthy.
  3. The retry only fires on a failed spawn or non-zero exit (opencodeRuntime.ts:678). A truncated-but-exit-0 read is accepted as authoritative and overwrites the previously good cache.

Net effect: status: "ready", "4 upstream providers connected through OpenCode.", and a third of the inventory quietly gone.

Impact

Major degradation or frequent failure

Models cannot be selected at all while truncated, and which models disappear shifts with inventory size and ordering, so it reads as random flakiness to users.

Version or commit

0.0.32-nightly.20260803.986

Environment

macOS 26.5.2, T3 Code Nightly desktop 0.0.32-nightly.20260803.986, OpenCode 1.18.11, OpenCode-only provider via the CLI inventory path (no external server URL).

Logs

~/.t3/caches/opencode.json at the time of capture:

{
  "instanceId": "opencode",
  "driver": "opencode",
  "enabled": true,
  "installed": true,
  "version": "1.18.11",
  "status": "ready",
  "checkedAt": "2026-08-03T20:05:43.172Z",
  "message": "4 upstream providers connected through OpenCode.",
  "auth": { "status": "authenticated", "type": "opencode" },
  "modelCount": 107
}

Suggested fixes

  • Fully drain stdout before treating the command as complete, so the result cannot depend on a pipe-drain race.
  • Treat an unparseable trailing model block as a hard error instead of skipping it — on this path it is a reliable truncation signal.
  • Do not replace a larger cached inventory with a materially smaller one from a single probe without corroboration.
  • Longer term, prefer the SDK provider.list path over parsing opencode models --verbose stdout, so connected is authoritative rather than synthesized from parse success.

Workaround

Restart T3 Code to force a fresh probe. It holds until the next refresh truncates again.

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