You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
opencodex version: 2.39.0 (bun runtime, macOS LaunchAgent, Apple Silicon)
Summary
Any user-set modelInputModalities on an OAuth-managed provider (e.g. kiro) is silently deleted on every service start. Both edit paths are affected: hand-editing ~/.opencodex/config.json while the service is stopped, and the sanctioned POST /api/providers management route. Reproduced 3/3 restarts before patching.
Key-auth providers (deepseek, kimi-code) don't go through the reconciliation, so identical config edits persist there — the asymmetry makes it look like "config edits don't work for kiro", but it's a wipe, not a write failure.
Root cause
reconcileOAuthProviders() (src/oauth/index.ts) reconciles providers that are registry-managed and authMode: "oauth" against the registry preset, field by field over OAUTH_RECONCILE_FIELDS:
for(constfieldofOAUTH_RECONCILE_FIELDS){if(JSON.stringify(prov[field])===JSON.stringify(preset[field]))continue;if(preset[field]!==undefined){prov[field]=cloneProviderField(preset[field])asnever;}else{deleteprov[field];// ← kiro preset has no modelInputModalities, so user values die here}}
modelInputModalities is in OAUTH_RECONCILE_FIELDS, but the kiro registry entry seeds no modelInputModalities (and kiro is absent from PROVIDER_ALIASES, so the models.dev metadata fallback doesn't fire either). Result: preset-undefined → delete prov[field] on every boot.
Notably the same file already guards against exactly this hazard for another field — requiresReasoningPlaceholderModels is deliberately excluded from reconciliation ("the delete-when-preset-undefined branch would wipe an explicit user opt-out on every startup"). modelInputModalities hits the same trap via the delete branch.
Steps to reproduce
kiro provider configured via OAuth (authMode: "oauth").
Add to its provider block in ~/.opencodex/config.json:
Start the service; GET /api/models shows the row as image-capable.
ocx stop && ocx service.
The key is gone from config.json, and /api/models reports the row as text-only again.
Impact
Vision-capable upstream models (kiro's Claude / GPT-5.6 rows — the kiro adapter already sends image blocks per docs/reference/adapters.md) can never be advertised as image-capable, so modality-respecting clients block image attachments.
Preserve explicit user values in reconcileOAuthProviders() — e.g. only overwrite (never delete) fields the user has set, or move modelInputModalities out of OAUTH_RECONCILE_FIELDS and rely on the fill-only enrichProviderFromRegistry() for preset propagation, as already done for requiresReasoningPlaceholderModels.
Local workaround currently in place
Removed "modelInputModalities" from OAUTH_RECONCILE_FIELDS in the installed 2.39.0 and re-submitted the provider block via POST /api/providers; modalities now survive restarts. Happy to send a PR if you tell me which approach you prefer.
Side note: while verifying, I also noticed the catalog advertises image input for deepseek-v4-flash / deepseek-v4-pro because they're sidecar-covered (noVisionModels) — documented behavior per sidecars.md / #3053, but in the GUI the badge is indistinguishable from native vision. Might be worth a UX tweak in a future release (distinguish native vision from sidecar coverage); not part of this bug report.
opencodex version: 2.39.0 (bun runtime, macOS LaunchAgent, Apple Silicon)
Summary
Any user-set
modelInputModalitieson an OAuth-managed provider (e.g.kiro) is silently deleted on every service start. Both edit paths are affected: hand-editing~/.opencodex/config.jsonwhile the service is stopped, and the sanctionedPOST /api/providersmanagement route. Reproduced 3/3 restarts before patching.Key-auth providers (deepseek, kimi-code) don't go through the reconciliation, so identical config edits persist there — the asymmetry makes it look like "config edits don't work for kiro", but it's a wipe, not a write failure.
Root cause
reconcileOAuthProviders()(src/oauth/index.ts) reconciles providers that are registry-managed andauthMode: "oauth"against the registry preset, field by field overOAUTH_RECONCILE_FIELDS:modelInputModalitiesis inOAUTH_RECONCILE_FIELDS, but the kiro registry entry seeds nomodelInputModalities(and kiro is absent fromPROVIDER_ALIASES, so the models.dev metadata fallback doesn't fire either). Result: preset-undefined →delete prov[field]on every boot.Notably the same file already guards against exactly this hazard for another field —
requiresReasoningPlaceholderModelsis deliberately excluded from reconciliation ("the delete-when-preset-undefined branch would wipe an explicit user opt-out on every startup").modelInputModalitieshits the same trap via the delete branch.Steps to reproduce
kiroprovider configured via OAuth (authMode: "oauth").~/.opencodex/config.json:GET /api/modelsshows the row as image-capable.ocx stop && ocx service.config.json, and/api/modelsreports the row as text-only again.Impact
docs/reference/adapters.md) can never be advertised as image-capable, so modality-respecting clients block image attachments.Suggested fixes (either or both)
modelInputModalitiesin the kiro registry entry (same approach as the [Bug]: GitHub Copilot provider: all models report text-only input — image attachments blocked #2941 fix) for the multimodal lineup (claude-, gpt-5.6-).reconcileOAuthProviders()— e.g. only overwrite (never delete) fields the user has set, or movemodelInputModalitiesout ofOAUTH_RECONCILE_FIELDSand rely on the fill-onlyenrichProviderFromRegistry()for preset propagation, as already done forrequiresReasoningPlaceholderModels.Local workaround currently in place
Removed
"modelInputModalities"fromOAUTH_RECONCILE_FIELDSin the installed 2.39.0 and re-submitted the provider block viaPOST /api/providers; modalities now survive restarts. Happy to send a PR if you tell me which approach you prefer.Side note: while verifying, I also noticed the catalog advertises image input for
deepseek-v4-flash/deepseek-v4-probecause they're sidecar-covered (noVisionModels) — documented behavior per sidecars.md / #3053, but in the GUI the badge is indistinguishable from native vision. Might be worth a UX tweak in a future release (distinguish native vision from sidecar coverage); not part of this bug report.