Skip to content

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

@luanweslley77

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 

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