# OpenCode Server Credential Redact Cycle

## The Bug

OpenCode server (v1.17.4) reads API keys from credential/config files on startup, stores them in process memory, then **overwrites the files with `***`** (redaction). On the next server restart, it reads `***` and sends that to the provider → **"Missing Authentication header"**.

**Affected files:**
- `~/.local/share/opencode/auth.json` — server credential store
- `~/.config/opencode/opencode.json` — CLI config (`provider.*.options.apiKey`)
- `~/.hermes/.env` — if OpenCode wrote `***` to environment file

**auth.json format:**
```json
{
  "providers": {
    "openrouter": { "apiKey": "***" },
    "deepseek": { "apiKey": "***" }
  }
}
```

## Trigger

Any server restart causes the cycle. Common restart causes:
- SIGTERM from systemd, service stop/start
- Manual kill + restart
- System reboot
- Process supervisor restarting after crash

## Full Diagnosis Path

1. **Observe server logs** — look for `error.error="AI_APICallError: Missing Authentication header"` in the output
2. **Check auth.json** — `cat ~/.local/share/opencode/auth.json` → all keys show `***`
3. **Check config** — `cat ~/.config/opencode/opencode.json | python3 -c "import json,sys; c=json.load(sys.stdin); print(c.get('provider',{}).get('openrouter',{}).get('options',{}).get('apiKey','N/A'))"` → `***`
4. **Confirm server binary type** — `file $(which opencode)` → ELF 64-bit Go binary (NOT npm source to patch)
5. **Check for env var support** — `strings /usr/local/lib/node_modules/opencode-ai/bin/opencode.exe | grep -i "OPENROUTER_API_KEY\|OPENAI_API"` → empty (no env var support coded)
6. **Check credential DB table** — `sqlite3 ~/.local/share/opencode/opencode.db "SELECT * FROM credential"` → typically empty

Research path: browse the archived GitHub repo (opencode-ai/opencode) for credential handling code in `internal/` packages.

## The Fix

1. **Stop the server** — `kill $(pgrep -f 'opencode serve')`  or `sudo systemctl stop opencode-server`
2. **Write full API keys to auth.json** (before starting!):
```json
{
  "providers": {
    "openrouter": { "apiKey": "sk-or-...REALKEY" },
    "deepseek": { "apiKey": "sk-...REALKEY" }
  }
}
```
3. **Also write to opencode.json** if it's also `***`:
   - `provider.openrouter.options.apiKey`
   - `provider.deepseek.options.apiKey`
4. **Start the server once** — `opencode serve --port 4096 --hostname 0.0.0.0`
5. **Verify** — `opencode models openrouter 2>/dev/null | grep nex` should return models without auth errors
6. **Do NOT restart the server** — the keys live only in process memory after startup

## Alternative: API Key as Request Body

The server's auth check follows this priority (from reverse-engineered JS):
```
request.body.apiKey ?? settings.apiKey ?? env_var
```
The `request.body.apiKey` path means you could also pass the key in each API request body, but that requires modifying every client call.

## Pitfalls

- **Writing keys to the file while the server is running has no effect** — it already read the old `***` values. Must stop first.
- **Restarting to apply any config change triggers the redact cycle** — plan for zero restarts once running.
- **Multiple consecutive restarts (exit 143 SIGTERM)** compound the problem — each restart re-reads `***`.
- **Server does NOT support environment variables** (OPENROUTER_API_KEY etc.) — checked via `strings` on the binary. Must use auth.json.
- **The credential table in the SQLite DB is separate from auth.json** — auth.json is the active store.
