-
Notifications
You must be signed in to change notification settings - Fork 24
Description
Summary
This tool's documentation states "configure API keys in your OpenCode config file" but this is impossible. The UI manages credentials in a database, while OpenCode itself stores them in ~/.local/share/opencode/auth.json. These are separate systems with no integration.
Critical Issue: No Way to Provide API Keys
The UI is misleading:
"Connect to AI providers using OAuth. For API keys, configure them in your OpenCode config file."
This is fundamentally incorrect:
- The application reads API keys from
~/.local/share/opencode/auth.json(backend/src/services/auth.ts:25) - OpenCode's config file (
~/.config/opencode/opencode.json) contains model/agent settings, NOT API keys - There is NO UI or documented workflow to add keys to
auth.json
Result: You cannot use your existing OpenCode credentials through this web UI at all.
Critical Issue: Hard-coded Paths Prevent Customization
All configuration paths are hard-coded in shared/src/config/env.ts:
AUTH_FILE: '.opencode/state/opencode/auth.json' // Line 26
CONFIG_DIR: '.config/opencode' // Line 25There are NO environment variables to override these. This means:
- You cannot mount your existing
auth.jsonto/workspace/.opencode/state/opencode/auth.json- the app overwrites it or fails with EROFS - You cannot mount your existing
opencode.jsoncto/workspace/.config/opencode/opencode.json- the app overwrites it on every startup viasyncDefaultConfigToDisk()(index.ts:185) - The only supported paths are exactly as hard-coded in the source
Any attempt to use your own config files requires either:
- Using
:romounts (prevents app from writing, breaks startup) - Commenting out
syncDefaultConfigToDisk()(requires code modification) - Accepting that your file will be overwritten on every restart
Critical Issue: Sync is Fundamentally Broken
The sync logic is one-directional and destructive:
File edit → OpenCode server reads it ✓
File edit → Backend database never sees it ✗
Backend database → File system ✓
Backend database → File system OVERWRITES on every startup ✗
Evidence:
- At startup, if the database has configs,
ensureDefaultConfigExists()returns immediately (index.ts:110-113) and never reads the file - Then
syncDefaultConfigToDisk()always runs (index.ts:214), writing database content to filesystem and overwriting your changes - There is no mechanism to sync filesystem → database except at initial startup when database is empty
Result: Maintaining config via files and UI simultaneously is impossible. Any file edit is silently lost on the next app restart.
Lack of Documentation
The README and codebase provide zero documentation for:
- How to mount custom config files
- That custom mounting is even possible
- The tradeoffs and caveats of doing so
- How to make
auth.jsonwork without modifying source code - The fact that the sync behavior assumes UI-only workflow
Users who try this (as I did) will discover:
- Their config gets overwritten on every restart
- No explanation in logs or documentation
- Must read source code to understand why
- Must modify source code to make it work
Impact
This affects ANY deployment scenario where:
- You want to use existing OpenCode credentials
- You want to manage config via files instead of the UI
- You want to deploy in a way that makes file-based workflows possible
For cloud deployments, users are forced into the UI-only workflow. For local deployments, users are forced to modify source code to achieve basic functionality that should be supported out of the box.
Proposed Solutions
For auth.json:
- Add an environment variable like
OPENCODE_AUTH_FILE_PATHto override the hard-coded path - Provide a documented way to mount existing
auth.jsonand have it work
For config sync:
- Make
syncDefaultConfigToDisk()check if file exists before writing - Or provide a file-as-source-of-truth mode
- Document the sync behavior and its limitations
For documentation:
- Explicitly state that file-based config is unsupported
- Or provide working examples of custom mounts with caveats
Workaround
The only workaround for me was to hack the source, but this is not of acceptable quality to do a pull request because it completely breaks behavior and expectations. It also has 2 limitations:
- The whole package must be restarted each time I edit either config file (it does not sync "on the fly");
- Changes to the config cannot be made through the UI (this is by my design, because without this limitation, the sync is only one-way: UI -> filesystem, never filesystem -> UI).
diff --git backend/src/index.ts backend/src/index.ts
index 8cc1d77..74e0c72 100644
--- backend/src/index.ts
+++ backend/src/index.ts
@@ -104,14 +104,15 @@ Prefer **pnpm** or **bun** over npm for installing dependencies to save disk spa
async function ensureDefaultConfigExists(): Promise<void> {
const settingsService = new SettingsService(db)
- const existingDbConfigs = settingsService.getOpenCodeConfigs()
-
- // Config already exists in database - nothing to do
- if (existingDbConfigs.configs.length > 0) {
- logger.info('OpenCode config already exists in database')
- return
- }
-
+ logger.warn('skip checking if config already exists')
+ // const existingDbConfigs = settingsService.getOpenCodeConfigs()
+
+ // // Config already exists in database - nothing to do
+ // if (existingDbConfigs.configs.length > 0) {
+ // logger.info('OpenCode config already exists in database')
+ // return
+ // }
+
// Try to import from existing OpenCode installation (highest priority)
const homeConfigPath = path.join(os.homedir(), '.config/opencode/opencode.json')
if (await fileExists(homeConfigPath)) {
@@ -211,7 +212,8 @@ try {
await cleanupExpiredCache()
await ensureDefaultConfigExists()
- await syncDefaultConfigToDisk()
+ logger.info('commented out await syncDefaultConfigToDisk')
+ // await syncDefaultConfigToDisk()
await ensureDefaultAgentsMdExists()
const settingsService = new SettingsService(db)
diff --git backend/src/services/settings.ts backend/src/services/settings.ts
index c6ec1d0..715a738 100644
--- backend/src/services/settings.ts
+++ backend/src/services/settings.ts
@@ -179,7 +179,16 @@ export class SettingsService {
// Check for existing config with the same name
const existing = this.getOpenCodeConfigByName(request.name, userId)
if (existing) {
- throw new Error(`Config with name '${request.name}' already exists`)
+ // throw new Error(`Config with name '${request.name}' already exists`)
+ logger.warn(`Config with name '${request.name}' already exists, overwriting...`)
+ this.db
+ .query(
+ `DELETE FROM opencode_configs
+ WHERE user_id = ? AND config_name = ?`)
+ .run(
+ userId,
+ request.name
+ )
}
const rawContent = typeof request.content === 'string'
diff --git docker-compose.yml docker-compose.yml
index 2657ec3..306ec9f 100644
--- docker-compose.yml
+++ docker-compose.yml
@@ -27,8 +27,12 @@ services:
- CLEANUP_INTERVAL_MINUTES=60
- DEBUG=false
volumes:
- - opencode-workspace:/workspace
- - opencode-data:/app/data
+ # - opencode-workspace:/workspace
+ # - opencode-data:/app/data
+ - ./workspace:/workspace
+ - ./data:/app/data
+ - /Users/tom/.local/share/opencode/auth.json:/workspace/.opencode/state/opencode/auth.json:ro
+ - /Users/tom/.config/opencode/opencode.jsonc:/workspace/.config/opencode/opencode.json:ro
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5003/api/health"]
@@ -37,8 +41,8 @@ services:
retries: 3
start_period: 40s
-volumes:
- opencode-workspace:
- driver: local
- opencode-data:
- driver: local
+# volumes:
+# opencode-workspace:
+# driver: local
+# opencode-data:
+# driver: local