Version: CLI 0.1.7 (core image from the 0.1.7 manifest). Harness: pi.
What happened
With a custom provider registered (PUT /v1/admin/custom-providers/...), the web-UI picker lists the custom model (surface.ts builds webuiModels from the live catalog, which includes custom models). Selecting it and sending a message fails with a bare HTTP 403 in the web UI.
Why
validateWebTurnModelOptions() (src/core/turn-options.ts:47-58) is called from app.turn() with enabledWebuiModels. When the org has no web-UI allowlist configured and no OpenRouter key, enabledWebuiModels is null, so the function falls back to DEFAULT_WEBUI_MODEL_IDS — the hardcoded registry list — which never contains custom models. The turn is refused with "that model is not enabled for the web UI"; postTurn maps refused to 403 (src/api/routes/turns.ts); and the web-UI client shows o?.message ?? o?.error ?? \HTTP ${status}`, but the refusal body carries reason, not message/error, so the operator only sees HTTP 403` with no explanation.
So the picker and the validator disagree: the picker offers the model, the validator refuses it. Workaround: configure an explicit allowlist in Governance → Allowed models that includes the custom model.
Suggested fix
- In
validateWebTurnModelOptions, treat the fallback set as DEFAULT_WEBUI_MODEL_IDS ∪ customModelCatalog().map(m => m.id) (same source the picker uses), or pass the picker's computed allowed list through.
- Surface
reason from a refused turn in the web UI instead of the bare status.
Repro
- Fresh org, no "Allowed models" configured, no OpenRouter key.
- Register any custom provider with one model via Admin → Onboarding → Custom providers.
- Web chat → pick the custom model → send a message. Expected: turn runs. Actual:
HTTP 403; nothing in error_events.
Version: CLI 0.1.7 (core image from the 0.1.7 manifest). Harness:
pi.What happened
With a custom provider registered (
PUT /v1/admin/custom-providers/...), the web-UI picker lists the custom model (surface.ts buildswebuiModelsfrom the live catalog, which includes custom models). Selecting it and sending a message fails with a bareHTTP 403in the web UI.Why
validateWebTurnModelOptions()(src/core/turn-options.ts:47-58) is called fromapp.turn()withenabledWebuiModels. When the org has no web-UI allowlist configured and no OpenRouter key,enabledWebuiModelsisnull, so the function falls back toDEFAULT_WEBUI_MODEL_IDS— the hardcoded registry list — which never contains custom models. The turn is refused with "that model is not enabled for the web UI";postTurnmapsrefusedto 403 (src/api/routes/turns.ts); and the web-UI client showso?.message ?? o?.error ?? \HTTP ${status}`, but the refusal body carriesreason, notmessage/error, so the operator only seesHTTP 403` with no explanation.So the picker and the validator disagree: the picker offers the model, the validator refuses it. Workaround: configure an explicit allowlist in Governance → Allowed models that includes the custom model.
Suggested fix
validateWebTurnModelOptions, treat the fallback set asDEFAULT_WEBUI_MODEL_IDS ∪ customModelCatalog().map(m => m.id)(same source the picker uses), or pass the picker's computedallowedlist through.reasonfrom arefusedturn in the web UI instead of the bare status.Repro
HTTP 403; nothing inerror_events.