Skip to content

[Bug]: OAuth provider reconciliation deletes user-set modelInputModalities on every startup (kiro) #3154

Description

@Caveat8937

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 (const field of OAUTH_RECONCILE_FIELDS) {
  if (JSON.stringify(prov[field]) === JSON.stringify(preset[field])) continue;
  if (preset[field] !== undefined) {
    prov[field] = cloneProviderField(preset[field]) as never;
  } else {
    delete prov[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

  1. kiro provider configured via OAuth (authMode: "oauth").
  2. Add to its provider block in ~/.opencodex/config.json:
    "modelInputModalities": { "claude-opus-4.6": ["text", "image"] }
  3. Start the service; GET /api/models shows the row as image-capable.
  4. ocx stop && ocx service.
  5. 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.
  • Same symptom class as [Bug]: GitHub Copilot provider: all models report text-only input — image attachments blocked #2941 (GitHub Copilot: all models text-only), which was fixed by seeding the registry; kiro just never got the same treatment, and user-side self-service is wiped by the reconciliation.

Suggested fixes (either or both)

  1. Seed modelInputModalities in 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-).
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions