fix: detect pre-#100 legacy model-override IDs in opencode.json#110
Merged
Conversation
Users who installed before PR #100 keep stale tier overrides like `models.deep = ["bedrock/claude-opus-4"]` in their opencode.json. resolveHarnessModels applied them unconditionally, stomping the plugin's correct Catwalk-canonical defaults (`anthropic/claude-opus-4-7` etc.) and crashing every subagent invocation with ProviderModelNotFoundError — pilot-planner and qa-reviewer hit first because they're typically delegated first. Add a pure offline pattern validator (src/model-validator.ts) that flags the specific legacy form `<bedrock|vertex|vertexai>/claude-<opus|sonnet|haiku>(-\d+)?` and maps each form to the Catwalk-preset replacement. Conservative: unknown or CRIS-prefixed IDs stay silent. resolveHarnessModels now warns once per unique bad ID (deduped per call) to stderr, naming the offending key and suggesting the fix. The bad value is still written to agentCfg.model so the user's intent is preserved — the warn is advisory, not corrective. `bunx … doctor` gains a model-overrides check that walks both plugin options.models and legacy top-level harness.models, prints a red-X line with the reason and remediation hint per invalid entry, and a green check when a models block exists with all valid entries. Silent on default installs. No user config is ever auto-rewritten. Plan: ~/.glorious/opencode/glorious-opencode/plans/pilot-agents-provider-model-not-found.md
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Detect legacy model-override IDs that stomp plugin defaults
Goal
Add a plugin-side safety net that detects pre-#100 legacy
bedrock/claude-<model>/vertex/<…>IDs set by users inopencode.json'smodelsblock and warns visibly at runtime and indoctor. Root cause:resolveHarnessModels()insrc/config-hook.tsapplies user overrides unconditionally without validation, so stale IDs from pre-#100 installs (likebedrock/claude-opus-4— missinganthropic.prefix, missing version digit) silently stomp the plugin's correct Catwalk IDs, producingProviderModelNotFoundErrorat subagent invocation. The PRIME's session that produced this plan repaired one user's opencode.json out-of-band; this plan ships the general fix so other pre-#100 users self-heal.Constraints
opencode.json.readFileSync, never staticimport.patchbump — defensive warning, no behavior change to happy paths).resolveHarnessModels's precedence (per-agent > per-tier > default still wins).global.anthropic.*prefix).bun testmust pass; new tests must cover valid + invalid cases.bun run build && bun run typecheckmust pass.Acceptance criteria
src/model-validator.tsexportsvalidateModelOverride(id: string): { valid: boolean; reason?: string; suggestion?: string }with conservative regex-based detection.{ valid: false, reason, suggestion }for every pre-fix: correct agent modes, model IDs, and add Catwalk-powered installer #100 legacy form:bedrock/claude-opus-4,bedrock/claude-sonnet-4,bedrock/claude-haiku-4,bedrock/claude-opus,vertex/claude-opus-4,vertexai/claude-opus-4.{ valid: true }for current Catwalk-canonical forms:anthropic/claude-opus-4-7,anthropic/claude-sonnet-4-6,anthropic/claude-haiku-4-5-20251001,anthropic/claude-haiku-4-5,bedrock/anthropic.claude-opus-4-7,bedrock/anthropic.claude-sonnet-4-6,bedrock/anthropic.claude-haiku-4-5-20251001-v1:0,vertexai/claude-opus-4-7@20250610.{ valid: true }for unknown-but-plausible forms (e.g.global.anthropic.claude-opus-4-7,openai/gpt-5, arbitrary Catwalk-like IDs we don't recognize). Only the specific legacy<bedrock|vertex|vertexai>/claude-<...>malformed pattern is flagged.resolveHarnessModels()insrc/config-hook.tscalls the validator on every override value it applies (both per-agent and per-tier paths, bothpluginOptions.modelsand legacyconfig.harness.modelssources). On invalid, it emits a singleconsole.warnper unique bad ID per call, format:[@glrs-dev/harness-opencode] Warning: invalid model override "<id>" (from <source>). <suggestion>. Run \bunx @glrs-dev/harness-opencode doctor` for details.whereis e.g.models.deepormodels.pilot-planner`.agentCfg.modelto the bad value (preserves user intent; warning is advisory only). Never aborts startup.doctor()insrc/cli/doctor.tsadds a new check that reads opencode.json's plugin-tupleoptions.models(and legacy top-levelharness.modelsfallback), runs every string value through the validator, and prints a red X line for each invalid entry with the key path, the offending value, the validator's suggestion, and the remediation hint "remove this key or replace with<suggestion>".test/model-validator.test.tsexists, covers valid + invalid + unknown cases, passes.test/config-hook.test.ts(new or extended) asserts the warn is emitted exactly once per unique bad ID even when multiple agents hit the same bad tier override, and thatagentCfg.modelstill gets set to the bad value.test/doctor.test.ts(new or extended) runs doctor against a fixture opencode.json with bad model IDs and asserts the red-X line + suggestion text appear in stdout.bun run build && bun run typecheck && bun testall pass.npm publish --dry-runshows the newdist/model-validator.js(and.d.ts) ship in the tarball..changeset/<slug>.mdwithpatchbump and a clear description.File-level changes
src/model-validator.ts (NEW)
validateModelOverrideand the internal pattern constants (for test import). Core detection logic: any ID matching^(bedrock|vertex|vertexai)/claude-(opus|sonnet|haiku)(-\d+)?$(noanthropic.subpath, no@YYYYMMDDvertex suffix, no-YYYYMMDD-v1:0bedrock suffix) is flagged as pre-fix: correct agent modes, model IDs, and add Catwalk-powered installer #100 legacy. Provide a suggestion that maps the legacy ID to its Catwalk-preset equivalent (look up via shared MODEL_PRESETS — import fromsrc/cli/install.tsif shape allows, otherwise duplicate the small mapping inline to avoid a CLI→runtime dependency).resolveHarnessModels(runtime) anddoctor(CLI).src/config-hook.ts
validateModelOverridefrom../model-validator.js. InresolveHarnessModels, after eachagentCfg.model = ...mutation, call the validator on the chosen value. Maintain aSet<string>of already-warned IDs scoped to the singleresolveHarnessModelsinvocation. For each invalid ID not yet warned, emitconsole.warn(...)with the source string (models.<agentName>for per-agent,models.<tier>for per-tier). Never abort.test/agents.test.tsand any config-hook test should still pass untouched.src/cli/doctor.ts
options.modelsblock (also check legacyconfig.harness.models). For every string or first-element-of-string-array value, callvalidateModelOverride. Accumulate invalid entries; print a red-X line per entry with key path + value + suggestion + remediation hint ("To fix: remove this key, or replace with<suggestion>"). If no invalid entries, print a green-check "all model overrides valid" line (only when amodelsblock exists — stay silent when there's none, to avoid clutter on default installs).bunx @glrs-dev/harness-opencode doctorafter hitting the error in TUI.test/model-validator.test.ts (NEW)
validateModelOverride. Three buckets: (a) valid — includes current Catwalk IDs, Anthropic aliases, Bedrock dated, Vertex dated, CRIS-styleglobal.*, unknownopenai/gpt-5. (b) invalid — every pre-fix: correct agent modes, model IDs, and add Catwalk-powered installer #100 legacy form. (c) edge — empty string, whitespace-only, non-string types (pass-through — TS typing gates non-strings but test anyway withas any). Assertvalidboolean and thatsuggestionmaps to the expected Catwalk-preset ID.test/config-hook.test.ts (NEW or EXTEND)
ls test/. If exists, extend; else create. Test: callresolveHarnessModelswith anagentsmap containing 3+ agents that all map to the same tier (e.g., all three todeep), and apluginOptions.models.deep = ["bedrock/claude-opus-4"]. Spy onconsole.warn. Assert: (a) each agent's.modelends up as"bedrock/claude-opus-4", (b)console.warncalled exactly once, (c) warn text includes"bedrock/claude-opus-4","models.deep", and the suggestion.test/doctor.test.ts (NEW or EXTEND)
test/merge-config.test.tsfor the fixture style — temp dir, write opencode.json, capture stdout). Rundoctor()against a fixture with{ plugin: [["@glrs-dev/harness-opencode", { models: { deep: ["bedrock/claude-opus-4"] } }]] }. Assert stdout contains a red-X-prefixed line withmodels.deep,bedrock/claude-opus-4, and the suggestion. Second fixture: valid overrides → green check line.bunx doctor.doctor()writes directly toconsole.log(it does), stdout capture viaspyOn(console, "log")is the pattern..changeset/.md (NEW)
bunx changeset→ choosepatch→ write description: "Detect pre-fix: correct agent modes, model IDs, and add Catwalk-powered installer #100 legacy model-override IDs (e.g.bedrock/claude-opus-4) in opencode.json and warn at plugin startup and indoctor. These invalid IDs were silently stomping plugin defaults and causingProviderModelNotFoundErrorat subagent invocation. The plugin now emits a one-line warn with the offending ID, its source key, and a Catwalk-preset suggestion. Runbunx @glrs-dev/harness-opencode doctorfor full detail. No auto-rewrite of user config."Test plan
bun test test/model-validator.test.ts— all cases green.bun test test/config-hook.test.ts— warn dedup + mutation semantics green.bun test test/doctor.test.ts— red-X fixture + green-check fixture green.bun test— entire suite still passes. In particulartest/agents.test.tsandtest/merge-config.test.tsuntouched.bun run build— dist/model-validator.js emitted, dist/cli.js and dist/index.js both include the new warn path.bun run typecheck— no errors.npm publish --dry-run—dist/model-validator.js+.d.tspresent in tarball. Size delta < 5 KB.node -e "const { validateModelOverride } = require('./dist/model-validator.js'); console.log(validateModelOverride('bedrock/claude-opus-4'))"returns{ valid: false, reason: '...', suggestion: 'bedrock/anthropic.claude-opus-4-6' }(or similar).~/.config/opencode/opencode.jsonin a throwaway shell, runbunx @glrs-dev/harness-opencode doctor, observe the red-X line. Restore. (Skip if CI sandbox; unit+integration tests cover it.)Out of scope
opencode.json. The plugin stays read-only on user config; only the CLI'sinstallsubcommand mutates it (and only via non-destructive merge).resolveHarnessModels's precedence rules. Per-agent beats per-tier beats plugin default — unchanged.~/.config/opencode/opencode.json.bak.1777167218); this plan's code does not touch user config.src/agents/prompts/code-searcher.mdalias-vs-dated-form review.anthropic/claude-haiku-4-5is a valid Anthropic API alias; not the current bug. Separate concern.AGENT_TIERSmapping.src/cli/install.ts). The installer's presets are already correct post-fix: correct agent modes, model IDs, and add Catwalk-powered installer #100; this plan does not touch it.Open questions