Repository navigation
Description
Client or integration
OpenCodex dashboard
Area
Dashboard
Summary
Opening the dashboard Integrations page (/#integrations) shows the red "Could not load integration state." notice, and every file-integration card stays in its placeholder state. The cause is server-side: GET /api/client-integrations returns HTTP 500 for the whole collection whenever the routed catalog contains a model whose ID holds a character the managed-selector grammar reserves (, or ]).
This is currently guaranteed to happen for any user signed into the Kimi coding subscription, because opencodex itself seeds kimi/k3[1m] into the catalog (src/providers/registry/model-seeds.ts, KIMI_CODING_K3_MODELS = ["k3", "k3[1m]", "k3-256k"]; the [1m] suffix is a local alias for the 1M context tier that is stripped before upstream requests). No user action involving Qoder is required: the client does not need to be installed, applied, or ever opened.
Expected: one client's inability to address a catalog row degrades to that client's own status (e.g. unsafe), and the collection read keeps answering 200 — the same isolation the route already applies to aside, and the same fail-closed classification readIntegrationState already returns for ClientPathError.
Reproduction
- Sign in a Kimi coding account (OAuth) so the catalog contains
kimi/k3[1m](visible, not disabled). - Start the proxy and open the dashboard Integrations page, or run:
curl -H "X-OpenCodex-API-Key: $ADMIN_TOKEN" http://localhost:10100/api/client-integrations- Observe:
{"error":"Qoder model ID cannot be represented by a managed array selector.","code":"integration_internal_error"}GET /api/client-integrations/qoder alone reproduces the same 500; other single-client endpoints (e.g. /api/client-integrations/opencode) answer 200.
Root cause, on current dev (2.91.0):
- The collection route
src/server/management/integration-routes.ts(GET /api/client-integrations) maps every client throughreadIntegrationState, but only wrapsasidein a per-client try/catch. readIntegrationState(src/integrations/state.ts) unconditionally builds the desired contribution viabuildIntegrationContribution— even when the client has no config file on disk (loadTargetreturnsokfor a missing file).buildQoderContribution(src/clients/config-export/qoder.ts) formats one[v2:provider=…,model=…]selector percustomModelsrow and throws whenformatSelectorConjunctionreturnsnull(src/integrations/merge.tsrejects values containing,or], which the v2 grammar reserves).kimi/k3[1m]contains].- The throw escapes to the route's outer catch and becomes
integration_internal_error(500), so the entire page fails because of one uninstalled client.
Notably, state.ts already documents this exact failure mode above its path-resolution guard ("The LIST route asks every client for its state, so letting that escape would answer 500 for the whole Integrations page"), but the guard only covers ClientPathError — the contribution build is unguarded. Sibling exporters handle the same situation gracefully: Droid omits unaddressable rows (config-export/droid.ts, if (!selector) continue) and ZCode ships the row without its model rule (config-export/zcode-store.ts); only Qoder throws. The throw is pinned by a test (tests/clients/qoder-client.test.ts, "refuses ambiguous model selectors instead of owning another provider's entry"), which makes sense for mutations — but the same builder is reused by the read-only status path.
Suggested direction (either works; we verified the first locally):
- Keep the throw on write paths, but contain it on the status path: give the failure a dedicated error type (e.g.
UnrepresentableSelectorErrornext toAmbiguousSelectorErrorinmerge.ts), have Qoder throw it, and catch exactly that inreadIntegrationState, classifying the clientunsafe/unresolvable-path(the same fail-closed answer as the existing Droid "nonempty catalog yields zero fragments" case). Mutations and previews still refuse loudly. - Alternatively isolate every client in the collection route the way
asideis isolated today. - Longer term, Qoder could follow the Droid convention and omit unaddressable rows from both the export document and the managed fragments.
Version
2.90.0 (pnpm global @bitkyc08/[email protected]); also reproduced on dev @ a4f335c (2.91.0)
Operating system
Windows 11 (build 26300)
Provider and model
kimi / k3[1m]
Logs or error output
$ curl -i -H "X-OpenCodex-API-Key: $ADMIN_TOKEN" http://localhost:10100/api/client-integrations
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{"error":"Qoder model ID cannot be represented by a managed array selector.","code":"integration_internal_error"}Screenshots and supporting files
Dashboard Integrations page shows a red notice banner: "无法加载集成状态。" (zh locale for "Could not load integration state.") while the other data surfaces (quota header, per-client cards fed by their own endpoints) load normally.
Redacted configuration
Relevant /api/models row (operator display-name override only; the ID itself comes from the built-in Kimi coding seed list):
{
"id": "k3[1m]",
"provider": "kimi",
"namespaced": "kimi/k3[1m]",
"disabled": false,
"contextWindow": 1048576
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Oct 22, 2026
Automated translation bookkeeping — detected language: English.
Ingwannu commentedon Oct 22, 2026
Confirmed on current dev
bf4eb1209672with actual loopback HTTP requests: the pre-fix regression returns 500 for bothk3[1m]and comma-containing model IDs, and the same construction exception also breaks the collection with an installed Qoder target.Draft fix #7519, commit
13ab9d58e6, catches only the dedicated selector-construction refusal in the shared status reader. Qoder becomesunsafe/unresolvable-path; sibling statuses remain visible. Preview/mutations continue refusing, exact ownership grammar is unchanged, matching target metadata is retained, and target/store contents remain unchanged on the refusal paths.Final focused/adjacent validation: 173 passed / 0 failed, including five new HTTP/error-propagation cases and layout/structure/size gates. Privacy and whitespace checks passed. Local typecheck and the final Pagefind docs-build hook each reached the 300-second cap, so neither is claimed complete; the PR stays Draft pending hosted validation and independent review. No live client/provider/configuration was changed, and this issue remains open until verified integration.
fix(qoder): provider-only catalog, replay-envelope refusal and isolat…
lidge-jun commentedon Oct 24, 2026
Fixed on dev by #7545 (5253164), which carries #7519 by @Ingwannu. A catalog model whose ID contains a reserved selector character (such as
kimi/k3[1m]) now marks only that client asunsafe/unresolvable-path, soGET /api/client-integrationsno longer returns 500 for the whole collection. This ships in the next release after 2.92.0; please reopen if it recurs.