Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9a4fece
Avoid persisting unauthenticated Zen fallback
friuns May 13, 2026
91f8562
Rename custom provider state file
friuns May 13, 2026
545c0de
Handle pending first-turn live state
friuns May 13, 2026
2eaf4bd
Load provider models before Codex model list
friuns May 13, 2026
6405a6e
Document Zen Docker auth model fixes
friuns May 13, 2026
abbc073
Show failed auth turn errors in chat
friuns May 13, 2026
7771c39
Add feedback action to auth errors
friuns May 13, 2026
9f32963
Hide duplicate persisted auth error overlay
friuns May 13, 2026
dbb820f
Document Docker provider auth regression workflow
friuns May 13, 2026
aa10f66
Add provider auth test checklist
friuns May 13, 2026
8d63a4d
Fix live error overlay de-duplication
friuns May 13, 2026
7ee94f8
Promote copied auth to Codex provider
friuns May 13, 2026
4d62f2c
Update wiki for copied auth provider promotion
friuns May 13, 2026
dfc0bd0
Fix Docker provider fallback runtime tests
friuns May 13, 2026
d939a17
Address provider review findings
friuns May 13, 2026
b51f44e
Fix turn error id collisions
friuns May 13, 2026
51bff49
Fix provider-scoped model selection drift
friuns May 13, 2026
cc6ddde
Keep routed thread during provider refresh
friuns May 13, 2026
dcffe94
Preserve thread route on provider switch
friuns May 13, 2026
d30089b
Update wiki for provider Docker cycle
friuns May 13, 2026
b10419d
Add Docker provider follow-up test tasks
friuns May 13, 2026
56d2de0
Prune passed Docker provider test task
friuns May 13, 2026
7cb2202
Enforce provider-backed model lists
friuns May 13, 2026
3d14db7
Populate provider Docker test backlog
friuns May 13, 2026
30033ed
Update provider Docker test backlog after run
friuns May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,29 @@
- For UI work, include dark-theme evidence in addition to the default/light-theme evidence unless the task is explicitly light-only.
- For refresh-persistence fixes, include a post-refresh screenshot that still shows the expected UI state.

## Docker Provider/Auth Regression Workflow

- Use this workflow when a change touches Docker startup, Codex auth detection, OpenCode Zen/OpenRouter/custom providers, provider model loading, app-server config, chat send/reply handling, or failed-turn error rendering.
- Build and test a packaged Docker image, not only the Vite dev server:
1. Run `pnpm run build`.
2. Run `pnpm pack --pack-destination /tmp`.
3. Build a local image that installs the packed `codexapp` tarball plus `@openai/codex`, with `CODEX_HOME=/codex-home` and command `codexapp --port ${PORT:-4190} --no-password --no-open --no-tunnel --no-login`.
4. Use OrbStack/Docker CLI. Do not rely on Docker Desktop.
- Start fresh isolated containers on unique localhost ports for at least these cases:
- no auth file: no `/codex-home/auth.json`; expect runtime OpenCode Zen fallback, `model_provider="opencode-zen"`, `model="big-pickle"`, send `hi`, wait for an assistant reply.
- invalid/expired auth file: mount an `auth.json` with token fields containing invalid/expired strings; expect Codex provider path, send `hi`, wait for final 401/auth error rendered in chat, verify `Send feedback`, reload the thread, verify the error persists, and verify no duplicate live `Thinking` overlay remains after persistence.
- malformed auth file: mount invalid JSON as `/codex-home/auth.json`; expect it to be treated as unusable auth and fall back to Zen, then send `hi` and wait for a reply.
- provider switch: start from OpenCode Zen, send `hi` and wait for a reply, switch the Provider settings selector to OpenRouter (do not change the model dropdown directly), send `hi` again and wait for a reply.
- Browser assertions must inspect conversation rows, not sidebar previews. A test is not passing just because the sidebar contains the sent text.
- Save screenshots under `output/playwright/` for all Docker browser cases and show them inline in the completion report.
- Before reporting success, include:
- tested URLs/ports,
- provider/config summary for each container,
- exact build/test commands,
- screenshot absolute paths,
- whether invalid auth persisted after reload and whether duplicate live overlay count was zero.
- If any Docker edge case fails, fix it before requesting PR review or merge.

## Mandatory CJS + TestChat Validation For Markdown/File-Link Features

- For any markdown parsing, link parsing, file-link rendering, or browse-link encoding change, verification in `TestChat` is mandatory before reporting completion.
Expand Down
88 changes: 88 additions & 0 deletions llm-wiki/raw/fixes/copied-auth-provider-promotion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copied Auth Provider Promotion Fix

Date: 2026-05-13

## Problem

In Docker no-auth mode, Codex Web Local starts with an OpenCode Zen runtime fallback. If the user switched the provider to OpenRouter while unauthenticated and then copied a valid `auth.json` into the mounted `CODEX_HOME`, the UI detected Codex auth but kept stale community-provider state.

Observed issues:
- Provider stayed on OpenRouter after valid Codex auth appeared.
- The Accounts badge stayed at `0` until a manual account refresh.
- The new-thread composer could show a generic `Model` label after provider promotion.
- The Settings feedback row could show stale `Send feedback / Issue detected` after recovery even when there was no visible current error.
- Sending on Codex worked after manually switching provider, proving the copied auth file was valid.

## Root Cause

The server read `webui-custom-providers.json` as authoritative whenever it existed. That file can contain community fallback provider state created during the unauthenticated phase. After `auth.json` appeared, the fallback provider state still supplied app-server provider flags and `/codex-api/free-mode/status` data.

The frontend also relied on the accounts snapshot for the Accounts count. A copied `auth.json` did not automatically import the active auth file into the accounts store.

Finally, provider-scoped new-thread model persistence applied to non-Codex providers but not to Codex. After provider promotion, the home composer could temporarily fall back to the generic `Model` placeholder instead of a concrete Codex model.

## Fix

Commit:
- `7ee94f83 Promote copied auth to Codex provider`

Implementation details:
- Added `shouldSuppressCommunityFreeModeForCodexAuth()` in `src/server/freeMode.ts`.
- `ensureDefaultFreeModeStateForMissingAuthSync()` now returns `null` when usable Codex auth exists and the existing provider state is only community fallback (`openrouter` or `opencode-zen` without a custom key).
- User-configured providers are preserved:
- OpenRouter with `customKey: true`
- OpenCode Zen with an explicit API key
- Custom endpoint provider
- `/codex-api/free-mode/status` now reports `hasCodexAuth`.
- `App.vue` uses `hasCodexAuth` to import a copied active `auth.json` into Accounts via `refreshAccountsFromAuth()` once.
- New-thread model persistence now uses provider-scoped slots for Codex as well as non-Codex providers.
- The Settings feedback row is shown only when a current visible error exists, not merely because historical diagnostics exist.

## Docker Validation

Fresh packaged image:

```text
codexui-local:e5e9-auth-promote-final2
```

Flow:
1. Start a fresh container with empty mounted `CODEX_HOME`.
2. Confirm initial provider is `opencode-zen`.
3. Switch provider to `openrouter`.
4. Copy `/Users/igor/.codex/auth.json` into the mounted `CODEX_HOME`.
5. Reload the UI.
6. Confirm provider changes to `codex`.
7. Confirm Accounts count becomes `1`.
8. Confirm the composer shows a concrete Codex model, not generic `Model`.
9. Confirm no stale `Send feedback / Issue detected` row appears.
10. Send `hi`; wait for a Codex reply.

Final validation result:

```json
{
"initialProvider": "opencode-zen",
"afterSwitchProvider": "openrouter",
"afterCopyProvider": "codex",
"afterCopyAccounts": 1,
"afterCopyHasIssue": false,
"finalProvider": "codex",
"finalHasIssue": false,
"stillBusy": false
}
```

Screenshot artifacts:
- `output/playwright/auth-promote-final2-01-noauth.png`
- `output/playwright/auth-promote-final2-02-openrouter.png`
- `output/playwright/auth-promote-final2-03-after-copy.png`
- `output/playwright/auth-promote-final2-04-reply.png`

## Verification Commands

```bash
pnpm test:unit src/server/freeMode.test.ts src/server/codexAppServerBridge.archive.test.ts src/composables/useDesktopState.test.ts src/api/codexGateway.test.ts
pnpm run build
pnpm pack --pack-destination /tmp
```
78 changes: 78 additions & 0 deletions llm-wiki/raw/fixes/opencode-zen-docker-auth-provider-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# OpenCode Zen Docker Auth and Provider Models Fix

Date: 2026-05-13

## Problem

Codex Web Local had two Docker startup edge cases around OpenCode Zen fallback and Codex auth:

1. In an authenticated Docker container, immediately polling `/codex-api/thread-live-state` after `turn/start` could return:

```text
thread <id> is not materialized yet; includeTurns is unavailable before first user message
```

The turn completed normally, but the bridge exposed this transient Codex state as `liveStateError.kind = "readFailed"`, making the chat look broken during first-turn startup.

2. In an unauthenticated Docker container, the model selector could appear empty or stale because frontend model loading called Codex `model/list` before `/codex-api/provider-models`. In OpenCode Zen fallback mode, provider models are authoritative; `model/list` can be slow, return Codex models, or fail independently.

## Root Cause

The live-state endpoint treated every `thread/read includeTurns=true` failure as a real read failure. Codex can briefly create a thread before the first user message is materialized, so that exact error is a pending state, not a terminal failure.

The model-loading helper fetched `model/list` first and only then attempted provider model discovery. This made no-auth Zen startup depend on a Codex model-list call that is not the source of truth for Zen models.

## Fix

Commits:
- `545c0dec Handle pending first-turn live state`
- `2eaf4bd3 Load provider models before Codex model list`

Implementation details:
- Added `isThreadMaterializationPendingError()` in `src/server/codexAppServerBridge.ts`.
- `/codex-api/thread-live-state` now maps that specific pending-materialization error to:
- `conversationState: { turns: [] }`
- `liveStateError: null`
- `isInProgress: true`
- Real `thread/read` failures still surface through `liveStateError`.
- `getAvailableModelIds()` now fetches `/codex-api/provider-models` first when provider models are included.
- If provider models are `exclusive` or `requireProviderModels` is true, it returns provider models without waiting on Codex `model/list`.
- Optional provider-model loading still falls back to `model/list` if provider models are unavailable.

## Docker Validation

Fresh image:

```text
codexui-local:e5e9-current
```

No-auth container:
- URL: `http://127.0.0.1:4191/#/`
- `config/read`: `model = "big-pickle"`, `model_provider = "opencode-zen"`
- App-server command includes Zen proxy flags.
- Sending `hi` returns an assistant reply.
- Model selector includes `big-pickle`, `deepseek-v4-flash-free`, and other Zen provider models.

Auth-mounted container:
- URL: `http://127.0.0.1:4192/#/`
- Mounted `/Users/igor/.codex/auth.json` to `/codex-home/auth.json`.
- `config/read`: `model = null`, `model_provider = null`
- App-server command has no Zen proxy flags.
- Sending `hi` returns an assistant reply.
- First-turn live-state polling does not expose the transient materialization error as `liveStateError`.

## Operational Notes

- For Docker validation, install the packed `codexapp` artifact during image build instead of using `pnpm dlx` at container runtime. Runtime `pnpm dlx` can re-download and extract dependencies on every start and can be killed under memory pressure.
- When validating no-auth Zen mode, trust `/codex-api/provider-models` and `/codex-api/free-mode/status` for provider models; `model/list` may still return Codex catalog rows from the Codex CLI.
- Browser verification should include a screenshot of the opened model selector after loading `http://127.0.0.1:4191/#/`.

## Verification Commands

```bash
pnpm test:unit src/server/codexAppServerBridge.archive.test.ts
pnpm test:unit src/api/codexGateway.test.ts src/composables/useDesktopState.test.ts
pnpm run build
```

113 changes: 113 additions & 0 deletions llm-wiki/raw/fixes/provider-selection-drift-docker-cycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Provider Selection Drift Docker Cycle

Captured on 2026-05-13 from branch `codex/provider-model-selection-drift`.

## Scope

This source records the packaged Docker provider validation for provider/model selection drift, thread continuity during provider switches, and custom provider behavior.

## Build and Containers

The test used a freshly packaged artifact from the current branch:

- `pnpm run build`
- `pnpm pack --pack-destination /tmp`
- Temporary Docker image `codexapp-provider-cycle:local`
- Image installed packed `codexapp-0.1.87.tgz` plus `@openai/codex@latest`
- Docker context: OrbStack/Docker CLI

Containers:

- No-auth: empty `CODEX_HOME`, port `4191`
- Auth-mounted: host `/Users/igor/.codex/auth.json` copied into isolated `CODEX_HOME`, port `4192`

## Confirmed Fixes

Two frontend changes were made before or during this cycle:

- Model selections are now stored as provider-tagged objects such as `{ "providerId": "opencode-zen", "modelId": "big-pickle" }`.
- Provider switching no longer runs `router.push({ name: 'home' })` after refresh, so a routed thread URL can remain stable through provider changes.

Related commits in this branch:

- `51bff49c` - provider-scoped model selection storage and validation.
- `cc6ddde9` - route sync no longer redirects home merely because the current sidebar list omits the routed thread.
- `dcffe94c` - provider switch handler no longer forces home navigation.

## Passing Evidence

No-auth Docker startup on `4191`:

- Settings provider was `OpenCode Zen`.
- Accounts badge was `0`.
- `/codex-api/free-mode/status` reported `provider=opencode-zen` and `hasCodexAuth=false`.
- `/codex-api/provider-models` reported `exclusive=true`, `source=opencode-zen`, and included `big-pickle`.
- Sending `hi` produced an assistant reply.

Auth-mounted Docker startup on `4192`:

- Settings provider was `Codex`.
- Accounts badge was `1`.
- Model dropdown contained Codex models only, including `GPT-5.5`, `GPT-5.4`, `GPT-5.4-mini`, `GPT-5.3-codex`, `GPT-5.3-codex-spark`, and `GPT-5.2`.
- The Codex model dropdown did not include `big-pickle`.
- Sending `hi` produced an assistant reply.

Thread continuity after provider switch:

- Starting URL: `http://localhost:4192/#/thread/019e2109-aacb-7612-a6cc-3740757594e0`
- After switching Codex to OpenCode Zen, the URL remained the same thread route.
- The existing conversation remained visible.

## Failing Evidence

Historical thread send after provider switch:

- With the URL preserved after switching Codex to OpenCode Zen, sending `hi provider opencode zen` failed in chat.
- Visible error: `RPC turn/start failed with HTTP 502: thread not found: 019e2109-aacb-7612-a6cc-3740757594e0`.
- This shows UI route continuity was fixed, but the backend session could not run that historical thread after provider switch.

OpenRouter selection mismatch:

- Settings could show `OpenRouter` selected while `/codex-api/free-mode/status` reported `enabled=false`, `hasCodexAuth=true`, and `provider=openrouter`.
- `/codex-api/provider-models` returned `source=provider`, `providerId=""`, and no models.
- The composer dropdown still showed Codex models.
- Expected behavior is either an activated OpenRouter model list or a clear blocking state, not Codex models under an OpenRouter selection.

NVIDIA NIM custom provider mismatch:

- Custom provider was set through `/codex-api/free-mode/custom-provider`.
- Config was `baseUrl=https://integrate.api.nvidia.com/v1` and `wireApi=chat`.
- `/codex-api/free-mode/status` reported `enabled=true`, `provider=custom`, `customBaseUrl=https://integrate.api.nvidia.com/v1`, and `wireApi=chat`.
- `/codex-api/provider-models` reported `source=custom`, `exclusive=true`, and 123 models.
- UI dropdown still showed Codex models.
- Searching `moonshotai/kimi-k2.5` in the UI model dropdown returned no results, despite successful NIM model discovery.
- Sending `hi provider nvidia nim` failed with `unexpected status 404 Not Found: 404 page not found, url: http://127.0.0.1:4192/codex-api/custom-proxy/v1/responses`.
- Expected behavior is for chat-completions providers to send through the chat proxy path with non-empty `messages`.

Groq custom provider:

- The local KeePass registry had OpenRouter and NVIDIA keys but no Groq key entry.
- A valid Groq send test was not completed.

## Screenshot Artifacts

Screenshots were captured under `output/playwright/`, including:

- `docker-noauth-settings.png`
- `docker-noauth-model-dropdown.png`
- `docker-noauth-hi-result.png`
- `docker-auth-settings.png`
- `docker-auth-model-dropdown.png`
- `docker-auth-hi-result.png`
- `docker-provider-switch-zen-result-fixed.png`
- `docker-provider-switch-openrouter-settings.png`
- `docker-provider-switch-custom-nim-result.png`

## Follow-up Test Inventory

The unresolved failures were copied into `whatToTest.md`:

- Provider-switched historical thread cannot send.
- OpenRouter provider can show selected while backend remains Codex.
- Custom NVIDIA NIM chat provider does not drive the UI model dropdown and sends to the Responses path.
- Groq custom provider was not completed due to missing key.
Loading