Skip to content

/connect in TUI does not persist plugin providers after instance dispose #20026

Closed

Description

@luanweslley77

Description

Bug Summary

OAuth providers registered by plugins via config() hook disappear from the TUI provider list after /instance/dispose, even though:

  • Plugin config() hook registers the provider correctly ✅
  • Plugin auth.loader() returns valid credentials ✅
  • client.auth.set() saves credentials to auth.json ✅
  • Provider appears in opencode auth login CLI picker ✅
    The provider only reappears after restarting OpenCode entirely.

Expected Behavior

  • Provider should appear in the provider list after OAuth completion
  • Models from the provider should be available for selection
  • No restart required

Actual Behavior

  • Provider does NOT appear in the provider list
  • Models from the provider are NOT available
  • Provider only appears after fully restarting OpenCode

Workaround

Restart OpenCode entirely after OAuth completion.

Technical Analysis

Root Cause

The issue is in packages/opencode/src/provider/provider.ts in the mergeProvider() function:

function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {
  const existing = providers[providerID]
  if (existing) {
    providers[providerID] = mergeDeep(existing, provider)
    return
  }
  const match = database[providerID]  // ← PROBLEM: Only checks models.dev
  if (!match) return  // ← Returns silently, provider not registered
  providers[providerID] = mergeDeep(match, provider)
}
The database is populated from ModelsDev.get() which only contains providers from models.dev, NOT providers registered by plugins via config() hook.
Timeline
1. Plugin loads → config() hook called
   └─> config.provider["qwen-code"] = { ... } ✅
2. provider.state() created
   └─> database["qwen-code"] = config.provider["qwen-code"] ✅
   └─> mergeProvider("qwen-code", ...) → database["qwen-code"] exists ✅
   └─> Provider registered ✅
3. User executes /connect → OAuth completes
   └─> client.auth.set() saves credentials ✅
   └─> OpenCode triggers /instance/dispose
4. New instance created
   └─> Config re-read from disk
   └─> config.provider["qwen-code"] = undefined ❌ (not persisted in opencode.json)
   └─> database["qwen-code"] = undefined ❌
   └─> mergeProvider("qwen-code", ...) → database["qwen-code"] missing ❌
   └─> Provider NOT registered ❌
Integration Test Results
Created comprehensive integration test (tests/integration/provider-refresh.ts) that confirms:
- ✅ File watcher triggers correctly
- ✅ Token cache invalidates correctly
- ✅ loader() receives valid credentials
- ✅ config() hook registers provider in-memory
- ❌ Provider does not appear in OpenCode provider list after dispose
Conclusion: The problem is NOT in the plugin - it's in OpenCode's provider state management.
Related Issues
- #13917 - Fixed for CLI (opencode auth login) via PR #13921, but TUI still affected
- #13231 - OAuth login methods not presented for plugin providers (still open)
- #18486 - Custom providers do not inherit provider loaders (still open)
Proposed Solutions
Option 1: Add Plugin Providers to database Before mergeProvider
Modify provider.state() to check plugin providers before calling mergeProvider():
for (const plugin of await Plugin.list()) {
  if (!plugin.auth) continue
  const providerID = ProviderID.make(plugin.auth.provider)
  
  // Check if plugin registered provider via config()
  const configProvider = config.provider?.[providerID]
  if (configProvider && !database[providerID]) {
    database[providerID] = fromConfigProvider(configProvider)
  }
  
  // ... rest of existing logic
}
Pros:
- Cleaner separation of concerns
- Doesn't modify user files
Cons:
- Requires OpenCode core changes
- More complex implementation
Option 2: Add API for Plugin Provider Registration
Expose an API for plugins to register providers persistently:
// New plugin API
client.provider.register({
  id: "qwen-code",
  name: "Qwen Code",
  npm: "@ai-sdk/openai-compatible",
  models: { ... }
})
Pros:
- Clean, official API
- Clear intent
Cons:
- Requires OpenCode core changes
- Longest timeline to resolution



### Plugins

opencode-qwencode-auth@git+https://github.com/luanweslley77/opencode-qwencode-auth.git, opencode-gemini-auth@latest 

### OpenCode version

1.3.7

### Steps to reproduce

1. Install plugin: `opencode-qwencode-auth@git+https://github.com/luanweslley77/opencode-qwencode-auth.git`
2. Start OpenCode TUI
3. Execute `/connect` command
4. Select the plugin's provider (e.g., "qwen-code")
5. Complete OAuth flow in browser
6. OAuth completes successfully, credentials saved to `~/.local/share/opencode/auth.json`
7. OpenCode triggers `/instance/dispose` internally
8. New instance is created
9. Execute `/connect` again

### Screenshot and/or share link

/

https://github.com/user-attachments/assets/38dd7924-4564-4f7b-a233-bf132c68d95e
https://github.com/user-attachments/assets/d21dda0a-2343-4db6-b0ec-530e241c2169

### Operating System

Fedora Linux 43 (KDE Plasma Desktop Edition) x86_64

### Terminal

Konsole 

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 30, 2026
  2. luanweslley77 commented on Mar 31, 2026

    @luanweslley77
    ContributorAuthor

    Supporting Evidence

    The opencode-gemini-auth plugin works correctly with /connect because its provider ID
    is "google", which IS registered in models.dev.
    After /instance/dispose, the Gemini provider continues to work because
    database["google"] exists in models.dev, so mergeProvider() succeeds.
    In contrast, custom providers like "qwen-code" are NOT in models.dev, so
    mergeProvider() fails silently after instance disposal.
    This confirms the root cause: providers must exist in models.dev OR be added to
    the database before mergeProvider() is called.

  3. added 11 commits that reference this issue on Mar 31, 2026
    2914c9e
    2ce55e7
    9f156db
    61f6e72
    9585f11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingcoreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions