fix(gateway): canonicalize fallback model override keys#85445
Conversation
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Keep open for normal maintainer review: the fallback-policy bug is source-reproducible on current main, the PR is focused, the supplied live-output proof applies to the current head, and I found no blocking correctness or security findings. Canonical path: Close this PR as superseded by #84946. So I’m closing this here and keeping the remaining discussion on #84946. Review detailsBest possible solution: Close this PR as superseded by #84946. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection on current main shows the fallback allowlist and requested override paths still raw-concatenate normalized provider/model refs, and the PR body supplies before/after output for the duplicate OpenRouter prefix and matching allow path. Is this the best way to solve the issue? Yes. Reusing the existing canonical Security review: Security review cleared: The diff only changes host-side model-ref key canonicalization and focused tests, with no dependency, workflow, secret, permission, network, or code-execution surface added. What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 1cd6dce07550. |
|
ClawSweeper PR egg ✨ Hatched: 🥚 common Mossy Patch Peep Hatch commandComment Hatchability rules:
Rarity: 🥚 common. What is this egg doing here?
|
f4ee9b7 to
0be4971
Compare
0be4971 to
805588a
Compare
805588a to
b0932cf
Compare
|
ClawSweeper applied the proposed close for this PR.
|
|
@steipete The close evidence treats #84946 as superseding this PR, but #84946 does not contain the gateway-side fix and current Evidence:
The durable review linked as close evidence also says current main still raw-concatenates these gateway fallback refs and that using Could this PR be reopened, or is there another PR/commit that already landed the full gateway-side fix? |
Summary
openrouter/openrouter/...in deny diagnostics.modelKey()helper for both the configured allowlist side and requested fallback override side.src/gateway/server-plugins.tsnow canonicalizes the three fallback comparison keys, andsrc/gateway/server-plugins.test.tscovers the OpenRouter deny diagnostic plus matching allow path.Motivation
src/plugins/runtime/runtime-llm.runtime.tsplus its test.mainstill had the gateway fallback raw-concat sites insrc/gateway/server-plugins.ts, and the focused repro still produced a duplicate OpenRouter provider prefix in the deny diagnostic.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Real behavior proof (required for external PRs)
upstream/mainfaf2a6cb9e(CI run26298182034succeeded) with headf4ee9b72ee. I ran a production-source gateway fallback smoke invocation throughcreateGatewaySubagentRuntime()plus the real gatewayagenthandler validation path, and also ran Vitest 4.1.7 vianode scripts/run-vitest.mjs.The production-source smoke script imports
src/gateway/server-plugins.tsandsrc/plugins/runtime/gateway-request-scope.ts, configures pluginvoice-callwithsubagent.allowModelOverride: trueandsubagent.allowedModels: ["openrouter/gpt-5.4-mini"], then callscreateGatewaySubagentRuntime().run()under plugin scope. The matching-allow case intentionally uses a malformed session key so the invocation stops in the real gatewayagenthandler validation before any agent/provider execution.Terminal capture from this branch, copied live output with local paths redacted:
{ "repo": "<local source checkout>", "proofKind": "production-source gateway fallback subagent invocation; no provider/network call", "policy": { "allowModelOverride": true, "allowedModels": [ "openrouter/gpt-5.4-mini" ] }, "deny": { "name": "deny disallowed provider-prefixed OpenRouter model", "ok": false, "error": "model override \"openrouter/gpt-5.5\" is not allowlisted for plugin \"voice-call\"." }, "allow": { "name": "allow matching provider-prefixed OpenRouter model", "ok": false, "error": "invalid agent params: malformed session key \"agent::allow-proof\"" }, "calls": { "addChatRun": 0, "removeChatRun": 0, "broadcast": 0, "registerToolEventRecipient": 0 }, "dedupeEntries": 0, "chatAbortControllers": 0, "checks": { "denyCanonical": true, "denyNoDuplicateProvider": true, "allowReachedGatewayValidation": true, "noAgentRunStarted": true } }Supplemental regression-test output from the same branch:
model override "openrouter/gpt-5.5"withoutopenrouter/openrouter/. The matching allowlisted request reaches real gatewayagenthandler validation (malformed session key "agent::allow-proof") instead of failing fallback override authorization, while counters confirm no agent/provider run started. The focused OpenRouter fallback tests and the fullserver-plugins.test.tsfile also pass.mainCI is not claimed green during local prep; this branch is based on the last-known-greenmainSHAfaf2a6cb9e, while newermainruns were pending/in-progress or had failing jobs when checked.mainbefore the code change withmodel override "openrouter/openrouter/gpt-5.5" is not allowlisted for plugin "voice-call".Root Cause (if applicable)
normalizeModelRef()can return{ provider: "openrouter", model: "openrouter/..." }when the request model already carries the provider prefix. The gateway fallback policy then formatted keys with raw`${provider}/${model}`instead of the canonical helper that dedupes an already provider-prefixed model id.Regression Test Plan (if applicable)
src/gateway/server-plugins.test.tsallowedModels: ["openrouter/gpt-5.4-mini"]rejectsprovider: "openrouter",model: "openrouter/gpt-5.5"without a duplicate provider prefix, and still allows the matchingprovider: "openrouter",model: "openrouter/gpt-5.4-mini"request.User-visible / Behavior Changes
Gateway fallback subagent override denial messages now report the canonical OpenRouter model ref instead of a duplicate provider-prefixed ref. Matching allowlisted provider-prefixed OpenRouter requests remain authorized and dispatch unchanged.
Diagram (if applicable)
N/A
Security Impact (required)
Yes, explain risk + mitigation: N/ARepro + Verification
Environment
voice-callwithsubagent.allowModelOverride: trueandsubagent.allowedModels: ["openrouter/gpt-5.4-mini"]Steps
allowedModels: ["openrouter/gpt-5.4-mini"].provider: "openrouter",model: "openrouter/gpt-5.5".provider: "openrouter",model: "openrouter/gpt-5.4-mini".node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/server-plugins.test.ts -t 'OpenRouter fallback'.Expected
openrouter/gpt-5.5and does not containopenrouter/openrouter/.provider: "openrouter"andmodel: "openrouter/gpt-5.4-mini".Actual
openrouter/openrouter/gpt-5.5.server-plugins.test.tsfile pass.Evidence
Attach at least one:
Human Verification (required)
src/gateway/server-plugins.test.ts; diff review confirmed onlysrc/gateway/server-plugins.tsandsrc/gateway/server-plugins.test.tschanged.Review Conversations
No review conversations exist yet for this PR.
Compatibility / Migration
Risks and Mitigations
provider: "openrouter"andmodel: "openrouter/gpt-5.4-mini".