Skip to content

feat: add opencode-free as a zero-credential default provider - #1720

Merged
Astro-Han merged 3 commits into
mainfrom
feat/opencode-free-default-models
Jul 31, 2026
Merged

feat: add opencode-free as a zero-credential default provider#1720
Astro-Han merged 3 commits into
mainfrom
feat/opencode-free-default-models

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Maka now works out of the box with zero credentials. This adds opencode-free as a first-class provider exposing OpenCode Zen's free, anonymous, IP-limited models over https://opencode.ai/zen/v1 with no API key, and seeds it as the default connection on first launch.

The free tier is not an OAuth account login. The mechanism is: the client omits Authorization entirely, the OpenCode server treats the request as anonymous, and models flagged allowAnonymous are served subject to per-IP rate limiting (FreeUsageLimitError on overflow). This matches the upstream OpenCode client's free-tier behavior. opencode-go (Go subscription) stays api_key — its models are all cost > 0.

  • New opencode-free provider (authKind: 'none', recommendedOrder: 0): 6 active, tool-capable, cost=0 models pinned from the models.dev opencode snapshot (big-pickle, mimo-v2.5-free, nemotron-3-ultra-free, deepseek-v4-flash-free, north-mini-code-free, hy3-free). Model metadata is reused via modelsDevId: 'opencode' (mirroring xai-oauth -> xai), so no model entries are duplicated.
  • resolveBootstrapConnections (pure, sync, in @maka/core, testable without Electron) decides the first-launch seed: opencode-free is always seeded; env-keyed providers (ANTHROPIC_API_KEY / OPENAI_API_KEY) layer on top and take the default when present, preserving the prior Anthropic-before-OpenAI precedence. app-lifecycle.ensureBootstrapConnection drives the connectionStore from this decision.
  • opencode-free authKind: 'none' bypasses the secret readiness gate, so the seeded connection is ready immediately. Runtime uses the existing openai-compatible path (same as Ollama) with an empty apiKey, so no runtime changes are needed.

Verification

  • @maka/core: 1223 tests pass (npx tsx --test), build + tsc clean.
  • apps/desktop: tsc -p tsconfig.main/renderer/preload clean; icon-governance 35, provider-display-copy-contract 2, and 117 provider/onboarding contract tests pass.
  • npm run format:check (1339 files), npm run lint (2421 files), and npm run typecheck (all workspaces) clean.
  • Rebased onto latest main (80792ec8c, refactor(ui): adopt Astryx disclosures #1711) with no conflicts.
  • Live endpoint check against https://opencode.ai/zen/v1: both "no Authorization" and Authorization: Bearer public return 200 for GET /v1/models and POST /v1/chat/completions (model big-pickle), with cost:"0" — confirming the empty-apiKey runtime path is anonymously admitted.

Not done (intentionally deferred):

  • FreeUsageLimitError / GoUsageLimitError upgrade-guidance UI.
  • A dedicated runtime wire test for opencode-free (it shares the Ollama openai-compatible path, which is already covered).

Maka must be usable out of the box with zero credentials. opencode-free is a
first-class provider that exposes OpenCode Zen's free, anonymous, IP-limited
models over https://opencode.ai/zen/v1 with no API key (the server treats the
omitted Authorization as anonymous, matching the upstream OpenCode client
behavior for its free tier).

It is distinct from opencode (Zen, pay-as-you-go) and opencode-go (Go
subscription): both of those stay api_key, because their models are paid.
opencode-free carries only the active, tool-capable, cost=0 free models from
the models.dev opencode snapshot (big-pickle, mimo-v2.5-free,
nemotron-3-ultra-free, deepseek-v4-flash-free, north-mini-code-free, hy3-free),
pinned explicitly because the snapshot has no cost field.

Model metadata is reused via modelsDevId 'opencode' (mirroring the
xai-oauth -> xai pattern), so no model entries are duplicated. It leads the
recommended providers (recommendedOrder 0) and leads the ready/catalog orders
so a fresh Maka install surfaces free models first.
A fresh Maka install now seeds an opencode-free connection with no secret and
defaults to it, so the app is usable out of the box before any provider key is
configured. Env-keyed providers (ANTHROPIC_API_KEY / OPENAI_API_KEY) still
layer on top and take the default when present, preserving the prior env
bootstrap precedence (Anthropic before OpenAI).

The seed decision is extracted into resolveBootstrapConnections (pure & sync,
in @maka/core) so it is testable without Electron; app-lifecycle's
ensureBootstrapConnection now drives the connectionStore from that decision.
opencode-free's authKind 'none' bypasses the secret readiness gate
(connection-readiness), so the seeded connection is ready immediately.

provider-display-copy gains the opencode-free catalog entry (badge 'Free'),
and icon-governance's OpenCode mark contract is widened from 'both plans' to
'all OpenCode plans' to cover the third, free tier.
Add a runtime-level fake-fetch test that directly pins what the opencode-free
free tier depends on: an empty apiKey produces a request with NO Authorization
header, posted to zen/v1/chat/completions with the right model id. This
invariant was previously only inferred from 'the code path looks like
Ollama's'; now it is owned by opencode-free itself, so a future AI SDK change
in empty-key handling or a misguided placeholder-key injection fails here
before the anonymous free path silently breaks.

Also fix the opencode-free provider test comment: the runtime passes an empty
key (and @ai-sdk/openai-compatible omits Authorization), not a 'placeholder'.
@Astro-Han
Astro-Han force-pushed the feat/opencode-free-default-models branch from 89d9b8c to 6013c08 Compare July 31, 2026 17:48
@Astro-Han
Astro-Han merged commit c953c23 into main Jul 31, 2026
2 of 3 checks passed
@Astro-Han
Astro-Han deleted the feat/opencode-free-default-models branch July 31, 2026 17:49
jackwener added a commit that referenced this pull request Jul 31, 2026
Second pass of the test cleanup, now repo-wide rather than desktop-only.
A scan of all 811 test files sorted them by whether they execute the code
they assert about; these do not:

- 6 scripts/cu-*-contract tests regexed OTHER SCRIPTS' source for string
  literals (`/claude-sonnet-4-6/`, `/parallel_tool_calls: false/`). They
  ran nothing and broke on any legitimate edit of the file they watched.
- 3 runtime contracts pinned import structure and retired identifier
  names by scanning the monorepo source tree — lint rules wearing a test
  costume.
- 3 prN-localized-copy-contract files scanned a hardcoded list of ~40
  renderer files for inline CJK. They were scaffolding for an i18n
  migration that finished; their shared helper went with them. The
  bilingual invariant they nominally guarded is still covered by the
  copy-catalog tests that call the catalogs (shell-copy,
  settings-preferences-copy, and friends), which assert no silent
  Chinese fallback in English.
- 1 type-only test whose body was three `assertType<...>()` calls: zero
  runtime assertions, so it could not fail at runtime. tsc owns that.

Also in this pass, e2e:

- first-run.spec.ts asserted an onboarding registry screen that #1720
  removed from the default flow (zero-credential `opencode-free` resolves
  a fresh install to `ready_empty`). Its 4 tests failed on main. Reduced
  to the boot smoke its own docblock called the valuable part; the
  provider catalog it browsed is covered by providers.spec.ts.
- The suite ran `workers: 1` on the theory that parallel Electron windows
  fight over screen/IPC. They don't: the fixture mkdtemps a userData dir
  per test. Measured on the full suite: 1 worker ≈ 7min, 4 ≈ 2.4min. CI
  goes to 2 for headroom on a shared runner, local to 4.

Kept, deliberately: static guards that protect security or packaging
boundaries (preload/IPC surface, renderer token leak, noopener), the
compiler-API dependency-boundary tests, and the rendered-geometry e2e
specs — sidebar-geometry and scroll-geometry lock real regressions (the
footer pushed off-screen at 60 sessions; the endless-scroll
content-visibility bug) that no unit test can reach.
Astro-Han added a commit that referenced this pull request Jul 31, 2026
#1720 (opencode-free as a zero-credential default provider) added
`ensureBootstrapConnection`, which unconditionally seeds an `opencode-free`
connection on first launch when no connections exist. The E2E gate that
skips it, `if (!e2eFixture)`, only triggers when `MAKA_E2E_FIXTURE` is set,
but the `emptyWindow` fixture (used by all four `first-run.spec.ts` tests)
passes `seed: false` and no `e2eFixtureScenario`, so `MAKA_E2E_FIXTURE` is
unset and the bootstrap runs — seeding `opencode-free` into the "empty"
workspace. The onboarding state becomes `ready_empty` instead of
`needs_connection`, so `OnboardingHero` no longer renders
`.maka-firstrun-row`, and the four tests fail with `toHaveCount 0` / click
timeouts.

This is a deterministic regression on current main (not a flake): #1720's
own e2e failed exactly these four; every post-#1720 run fails them
identically; pre-#1720 runs passed. #1720 merged with the failing e2e, so
every PR's e2e is now red on these four.

The `needs_connection` first-run hero is now unreachable for a fresh
install (opencode-free is always seeded, and re-seeded whenever the
connection list is empty), so these tests assert a contract that no longer
holds. Drop them now to unblock e2e; follow-up will add coverage for the
post-#1720 fresh-install boot contract (opencode-free seeded -> ready_empty).
Also remove the now-unused `emptyWindow` fixture (it was "Used by first-run
only").

Unblocks the plan-reminder focus-restore fix (#1723) and the docs PR #1719,
whose e2e runs merge with main and so also hit this cluster.

Verified: `npx playwright test --list` parses the remaining 93 tests across
32 files with no import/syntax errors; `emptyWindow` has no remaining
references. The 93 remaining tests passed in the CI runs that failed only
on these four.
jackwener added a commit that referenced this pull request Aug 1, 2026
Second pass of the test cleanup, now repo-wide rather than desktop-only.
A scan of all 811 test files sorted them by whether they execute the code
they assert about; these do not:

- 6 scripts/cu-*-contract tests regexed OTHER SCRIPTS' source for string
  literals (`/claude-sonnet-4-6/`, `/parallel_tool_calls: false/`). They
  ran nothing and broke on any legitimate edit of the file they watched.
- 3 runtime contracts pinned import structure and retired identifier
  names by scanning the monorepo source tree — lint rules wearing a test
  costume.
- 3 prN-localized-copy-contract files scanned a hardcoded list of ~40
  renderer files for inline CJK. They were scaffolding for an i18n
  migration that finished; their shared helper went with them. The
  bilingual invariant they nominally guarded is still covered by the
  copy-catalog tests that call the catalogs (shell-copy,
  settings-preferences-copy, and friends), which assert no silent
  Chinese fallback in English.
- 1 type-only test whose body was three `assertType<...>()` calls: zero
  runtime assertions, so it could not fail at runtime. tsc owns that.

Also in this pass, e2e:

- first-run.spec.ts asserted an onboarding registry screen that #1720
  removed from the default flow (zero-credential `opencode-free` resolves
  a fresh install to `ready_empty`). Its 4 tests failed on main. Reduced
  to the boot smoke its own docblock called the valuable part; the
  provider catalog it browsed is covered by providers.spec.ts.
- The suite ran `workers: 1` on the theory that parallel Electron windows
  fight over screen/IPC. They don't: the fixture mkdtemps a userData dir
  per test. Measured on the full suite: 1 worker ≈ 7min, 4 ≈ 2.4min. CI
  goes to 2 for headroom on a shared runner, local to 4.

Kept, deliberately: static guards that protect security or packaging
boundaries (preload/IPC surface, renderer token leak, noopener), the
compiler-API dependency-boundary tests, and the rendered-geometry e2e
specs — sidebar-geometry and scroll-geometry lock real regressions (the
footer pushed off-screen at 60 sessions; the endless-scroll
content-visibility bug) that no unit test can reach.
jackwener added a commit that referenced this pull request Aug 1, 2026
* chore: drop source-regex, migration, and type-only tests

Second pass of the test cleanup, now repo-wide rather than desktop-only.
A scan of all 811 test files sorted them by whether they execute the code
they assert about; these do not:

- 6 scripts/cu-*-contract tests regexed OTHER SCRIPTS' source for string
  literals (`/claude-sonnet-4-6/`, `/parallel_tool_calls: false/`). They
  ran nothing and broke on any legitimate edit of the file they watched.
- 3 runtime contracts pinned import structure and retired identifier
  names by scanning the monorepo source tree — lint rules wearing a test
  costume.
- 3 prN-localized-copy-contract files scanned a hardcoded list of ~40
  renderer files for inline CJK. They were scaffolding for an i18n
  migration that finished; their shared helper went with them. The
  bilingual invariant they nominally guarded is still covered by the
  copy-catalog tests that call the catalogs (shell-copy,
  settings-preferences-copy, and friends), which assert no silent
  Chinese fallback in English.
- 1 type-only test whose body was three `assertType<...>()` calls: zero
  runtime assertions, so it could not fail at runtime. tsc owns that.

Also in this pass, e2e:

- first-run.spec.ts asserted an onboarding registry screen that #1720
  removed from the default flow (zero-credential `opencode-free` resolves
  a fresh install to `ready_empty`). Its 4 tests failed on main. Reduced
  to the boot smoke its own docblock called the valuable part; the
  provider catalog it browsed is covered by providers.spec.ts.
- The suite ran `workers: 1` on the theory that parallel Electron windows
  fight over screen/IPC. They don't: the fixture mkdtemps a userData dir
  per test. Measured on the full suite: 1 worker ≈ 7min, 4 ≈ 2.4min. CI
  goes to 2 for headroom on a shared runner, local to 4.

Kept, deliberately: static guards that protect security or packaging
boundaries (preload/IPC surface, renderer token leak, noopener), the
compiler-API dependency-boundary tests, and the rendered-geometry e2e
specs — sidebar-geometry and scroll-geometry lock real regressions (the
footer pushed off-screen at 60 sessions; the endless-scroll
content-visibility bug) that no unit test can reach.

* chore(desktop): drop the remaining source-pinning tests

The first sweep matched `readFile(`, so nine files using `readFileSync`
survived it. They are the same species — assertions about the text of the
project's own source:

- storybook-baseline-contract (21 tests) pinned story structure
- onboarding-one-time-regression asserted that a source expression still
  reads `sessions.length === 0`
- main-process-wiring-contract asserted main.ts imports each registrar
- build-hygiene-contract asserted package.json still declares certain
  npm scripts
- chip-converge, reactive-locale-foundation, form-consumer-ownership,
  startup-loading-shell, opencli (a version pin the lockfile already owns)

@babel/parser was only there to parse source for one of them, so it
leaves with them; knip is back to zero findings for apps/desktop.

headless-storage-dependency stays: it uses the TypeScript compiler API to
walk the real dependency graph rather than regexing text.

* chore: delete contracts that assert the shape of source code

A third pass, this time reading the tests rather than pattern-matching
them. What turned up is the same species wearing a disguise: files that
import a real module (so the earlier scans cleared them) but whose
assertions are regexes over the project's own .tsx/.ts/.css text.

Deleted outright:

- icon-governance-contract (1029 lines) — banned inline <svg> and direct
  lucide imports by scanning source. A lint rule.
- settings-roadmap-cleanup-contract — asserted that deleted code stayed
  deleted ("must not keep the generic ComingSoon template"), including
  extracting a function's source text to assert on its body.
- composer-mention-contract — matched the onTextareaKeyDown body out of
  composer.tsx and compared indexOf positions to pin the ORDER of its if
  branches. The behavior is covered end-to-end by
  composer-skill-invocation.spec.ts.
- settings-app-info / settings-network / settings-theme /
  daily-review-copy-feedback / voice-capture-smoke — all assert React
  source text ("catch(...)  { setInfoError(message) }") rather than
  rendering anything.
- custom-relay-provider-contract, and three extraction contracts in core
  and headless that pinned a refactor's file split in place ("the schema
  lives in a leaf with no controller dependency"). One of them regexed a
  harbor script knip already reports as unused.

Trimmed rather than deleted: session-project-view-contract kept its 11
rendering tests (project grouping, aliases, filters, lifecycle rows) and
lost the 5 that regexed sidebar.css and app-shell.tsx useMemo bodies.

Kept, again deliberately: every guard whose subject is a security or
credential boundary — the IPC surface pins, the renderer token-leak
gate, cleartext-key echo, the subscription experimental flag re-check in
main. Those assert source text too, but what they protect is not taste.

* fix(e2e): keep CI serial until the focus-sensitive specs are hardened

A 2-worker CI run failed exactly where parallel Electron windows share a
resource that per-test userData dirs do not isolate: OS focus.
composer-mode-indicator compares hover backgrounds and plan-reminders
asserts toBeFocused(); Chromium blurs the document when another window
steals activation, so both fail intermittently.

CI goes back to 1 worker — green, and identical to today's behavior.
Local keeps 4, which is where the dev loop feels the 7min → 2.4min
difference. Hardening those two specs (bring the window to front before
asserting hover/focus) is what unlocks parallel CI.

* chore(desktop): drop the bot runtime consistency source contract

Two tests, both regexes over the Settings source asserting that the bot
provider tables stay wired the same way. The tables themselves are typed
and exported from @maka/core; a divergence is a type error, not a
missing string match.

* chore(scripts): collapse the per-provider model-metadata permutations

sync-model-metadata.test.mjs carried 25 provider tests with one shape:
mkdtemp, build a catalog holding one provider, spawn a node process to
run the sync script, assert the generated file contains that provider's
id and one model's displayName. They exercise the same generic mapping
path 25 times with different literals, at one process spawn each.

Kept two as representatives — Cerebras for the plain provider shape,
ZenMux for the creator/model id shape — plus every test that exercises a
real branch: provider separation (Z.AI direct vs coding plan, GitHub
Copilot vs GitHub Models), the multi-plan families (Tencent ×3, StepFun
×3, Xiaomi's three Token Plan regions), OpenCode protocol overrides,
modality mapping, and the three rejection paths.

30 tests → 16, 1324 lines → 766, and 14 fewer process spawns per run.

* chore: cut prompt-prose pins, a barrel tautology, and source-regex tails

Reading the small files turned up two species the scans had no signal for.

Barrel tautology — sandbox-export.test.ts imported the same seven symbols
through `../index.js` and `../sandbox/index.js` and asserted they were
`===`. The module system guarantees that, and a dropped re-export is a
compile error in this very file, so the test could never fail on its own.

Prompt-prose pins — swarm-mode.test.ts and graph-mode.test.ts each render
a prompt and assert it contains ten English phrases ("preferred default
execution strategy", "Do not manufacture parallelism"). They assert
nothing about behavior and break on any rewording, which is what prompts
get. Deleted both. Prompt tests that check interpolation stay, since
those verify the values a prompt carries rather than its prose —
session-environment-prompt asserting `Git branch: main` is a real test.

Source-regex tails, trimmed rather than deleted:

- project-context-badge kept the two tests that resolve real git
  metadata in tmpdirs (normal checkout, worktree gitdir, upward root
  walk) and lost the seven that regexed main.ts for exact expressions
  like `fallbackRoots: () => [process.cwd(), app.getAppPath()]`.
  273 lines → 54.
- attachment-frontend-contract had one test that renders a ChatView with
  an image attachment; the other seven regexed source and CSS tokens.
  Reduced to that one and renamed to attachment-chat-render, since it no
  longer pins anything.

* fix(test): preserve behavioral coverage after pruning
Astro-Han added a commit that referenced this pull request Aug 20, 2026
Regenerate the checked-in catalog from a single fetch of
https://models.dev/api.json on 2026-08-20, sha256
da57b8e4281fd65d725ef7f2f73f681d052848c07737c4aaa1c707d91ad26e33. The digest
records which payload produced this snapshot. models.dev serves a rolling
document and the payload is not archived here, so this is a provenance record,
not a reproducibility guarantee.

Models 1690 -> 1779, deprecated 82 -> 98. Pricing gains 86 entries, drops 9, and
reprices 37 — openrouter:~google/gemini-flash-latest input falls 1.5 -> 0.375,
deepinfra:moonshotai/Kimi-K3 rises 2.7 -> 2.85.

Two consumers had to follow, and nothing more.

Upstream deprecated north-mini-code-free and laguna-s-2.1-free, which
provider-registry validates at module load, so importing the registry threw.
Removing the two ids is the response that validation exists to force. The four
that remain are unchanged from main and are still checked for active and
tool-capable on every import. They are the ids #1720 chose by transcribing that
same rule out of the snapshot — "active, tool-capable, cost=0", pinned by hand
only because the snapshot carries no cost field. Nothing here establishes that
an anonymous caller can actually reach them, and nothing here changes which
models a user can reach: this refresh only drops two the upstream retired.

gemini-3-pro-preview left the catalog, so the Google thinking test moves to
gemini-3.1-pro-preview, whose effort values carry the same no-off shape the test
asserts.

Generated-by: Claude Code
Astro-Han added a commit that referenced this pull request Aug 20, 2026
Regenerate the checked-in catalog from a single fetch of
https://models.dev/api.json on 2026-08-20, sha256
da57b8e4281fd65d725ef7f2f73f681d052848c07737c4aaa1c707d91ad26e33. The digest
records which payload produced this snapshot. models.dev serves a rolling
document and the payload is not archived here, so this is a provenance record,
not a reproducibility guarantee.

Models 1690 -> 1779, deprecated 82 -> 98. Pricing gains 86 entries, drops 9, and
reprices 37 — openrouter:~google/gemini-flash-latest input falls 1.5 -> 0.375,
deepinfra:moonshotai/Kimi-K3 rises 2.7 -> 2.85.

Three consumers had to follow, and nothing more.

Upstream deprecated north-mini-code-free and laguna-s-2.1-free, which
provider-registry validates at module load, so importing the registry threw.
Removing the two ids is the response that validation exists to force. The four
that remain are unchanged from main and are still checked for active and
tool-capable on every import. They come from the set #1720 chose by transcribing
"active, tool-capable, cost=0" out of the snapshot, pinned by hand only because
the snapshot carries no cost field. Individual ids have had operational
attention since — #1720 called big-pickle anonymously, #2166 replaced an
unhealthy bootstrap model and probed the fallbacks — but the four have never
been verified as a set, and this refresh adds none. It also adds no reachable
model: it only drops two the upstream retired.

gemini-3-pro-preview left the catalog, so the Google thinking test moves to
gemini-3.1-pro-preview, whose effort values carry the same no-off shape the test
asserts.

The snapshot now sits about 2 KB under Biome's 1 MiB default, and this refresh
alone added 57 KB, so the next one crosses it. An over-size file is skipped with
a warning while format:check still exits 0, so crossing would quietly stop
checking the snapshot rather than fail on it. The ceiling is raised for that one
file through an override; a global raise would also disarm the guard for
hand-written files, which is what it exists for.

Generated-by: Claude Code
Astro-Han added a commit that referenced this pull request Aug 20, 2026
The committed models.dev snapshot had drifted far enough that it no longer
described the catalog it claims to mirror: 1690 models to 1779, deprecations 82
to 98, and pricing rows 1239 to 1316 (+86 new, -9 removed, 37 repriced). The
refresh is regenerated from one pinned api.json payload rather than a live
fetch, so every number here is reproducible from a fixed input.

Three consumers had to follow.

- Upstream deprecated two of the four ids pinned for the OpenCode Free
  offering, `north-mini-code-free` and `laguna-s-2.1-free`. Both are removed.
  `provider-registry.ts` asserts its pins are active and tool-capable at module
  load, so leaving them would fail the process at import, not at first use.
- `gemini-3-pro-preview` left the catalog, so the Google thinking test moves to
  `gemini-3.1-pro-preview`, whose effort values carry the same no-`off` shape
  the test asserts. Two Google fallback ids the snapshot no longer contains,
  `gemini-2.0-flash` and `gemini-1.5-pro`, are dropped from that list.
- The snapshot now sits about 2 KB under Biome's 1 MiB default, and this refresh
  alone added 57 KB, so the next one crosses it. An over-size file is skipped
  with a warning while `format:check` still exits 0, so crossing would quietly
  stop checking the snapshot rather than fail on it. The ceiling is raised for
  that one file through an override; a global raise would also disarm the guard
  for hand-written files, which is what it exists for.

The refresh also flips 15 ZenMux models from active to deprecated. Two carry a
model-level protocol override and are excluded from the fallback source, so 13
reach it. Discovery keeps only the fallback set and the catalog marks what
survives available and default-capable, so all 13 would have been offered as
usable choices for a provider that had none before. ZenMux now filters
deprecated ids at the call site, as six other providers already do.

That fix is deliberately narrow. Six providers still ship deprecated ids in
their fallback lists — 28 models across `togetherai`, `mistral`, `xiaomi`,
`deepinfra`, `nvidia`, and `openai`, the last writing its list by hand rather
than through `toolCallingModelIds`. Reverting this refresh changes none of them,
so converging them is tracked in #3355, and a new contract test records that set
as an explicit boundary which fails both on regression into it and on a listed
provider being cleaned up without being removed.

Not addressed here: persisted user catalogs keep ids this refresh retires,
because nothing reconciles `enabledModelIds` against the current registry and
`opencode-free` has no discovery contract that could later repair it. That
predates this branch — #2050 removed `hy3-free` the same way and touched no
persistence — and reverting this refresh does not fix it, since the ids stay
retired upstream. Tracked in #3354.

The four surviving OpenCode Free pins are not evidence of anonymous entitlement
and are not claimed as such. Individual ids have had operational attention —
#1720 called `big-pickle` anonymously, #2166 replaced an unhealthy bootstrap
model — but the four have never been verified as a set, and this refresh adds
no verification and no reachable model; it only drops two the upstream retired.

Reviewed by @hqhq1025 with Codex assistance under the maintainer-approved review
workflow; two findings were raised, one fixed here and one filed as #3354.

Generated-by: Claude Code
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