fix: commit routing-pinned key ID to reserved BifrostContextKeyAPIKeyID after PreRequestHook unblock - #4359
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
📝 WalkthroughWalkthroughThis PR introduces a routing-specific context key for API key pinning that the governance routing plugin populates, then commits into the canonical key-selection field during pre-request hook processing. This ensures routing-pinned keys take precedence over caller-supplied pins while maintaining separation of concerns between the plugin-scoped routing context and the reserved key-selection context. ChangesRouting-pinned API key context commitment in pre-request hooks
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Comment |
Confidence Score: 5/5Safe to merge. The change is narrowly scoped to the routing-pin propagation path, all existing reserved-key invariants are preserved, and the new commit step is correctly ordered after the write block is lifted. The defer-to-explicit-call refactor and the two-phase write (staging key to reserved key) are both correct. The blockRestrictedWrites atomic flag is only false when the commit runs, so the write to BifrostContextKeyAPIKeyID succeeds. Both call sites of applyRoutingRules are inside PreRequestHook, so the commit in RunPreRequestHooks always fires after the governance write. Tests cover all meaningful cases. No files require special attention. Important Files Changed
Reviews (1): Last reviewed commit: "fix: routing rule key pinning fixes" | Re-trigger Greptile |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
core/bifrost.go (1)
6854-6890:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClear the routing pin staging key per request.
BifrostContextKeyRoutingPinnedAPIKeyIDnow lives on the shared mutable context, but this path never clears it. On a reused/long-livedBifrostContext(the realtime/WebSocket case called out above), a pin from request A will still be present when request B has no routing match, so Lines 6887-6889 re-pin request B to the old key and override the caller pin you intended to preserve.💡 One way to make the commit request-scoped and keep unblock panic-safe
func (p *PluginPipeline) RunPreRequestHooks(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) { // If the skip plugin pipeline flag is set, skip the plugin pipeline if skipPluginPipeline, ok := ctx.Value(schemas.BifrostContextKeySkipPluginPipeline).(bool); ok && skipPluginPipeline { return } - ctx.BlockRestrictedWrites() - for _, plugin := range p.llmPlugins { - ... - } - ctx.UnblockRestrictedWrites() + ctx.ClearValue(schemas.BifrostContextKeyRoutingPinnedAPIKeyID) + func() { + ctx.BlockRestrictedWrites() + defer ctx.UnblockRestrictedWrites() + for _, plugin := range p.llmPlugins { + ... + } + }() // Commit the routing-rule key pin... if pin, ok := ctx.Value(schemas.BifrostContextKeyRoutingPinnedAPIKeyID).(string); ok { + ctx.ClearValue(schemas.BifrostContextKeyRoutingPinnedAPIKeyID) if pin = strings.TrimSpace(pin); pin != "" { ctx.SetValue(schemas.BifrostContextKeyAPIKeyID, pin) } } }Based on learnings,
(*schemas.BifrostContext).SetValuemutates the shared context in place, so values persist on reused contexts unless they are explicitly cleared.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/bifrost.go` around lines 6854 - 6890, The staging routing pin BifrostContextKeyRoutingPinnedAPIKeyID is left on a shared BifrostContext and can leak between requests; fix by explicitly clearing/resetting that key per-request (e.g., call ctx.SetValue(schemas.BifrostContextKeyRoutingPinnedAPIKeyID, "") or remove it) at the start of request handling (before running plugin PreRequestHook and before any blocked/unblocked phases) and ensure the commit logic that reads the pin (the block that calls ctx.SetValue(schemas.BifrostContextKeyAPIKeyID, pin)) treats an empty string as “no pin” so no stale value is re-applied; reference symbols: BifrostContextKeyRoutingPinnedAPIKeyID, ctx.SetValue, PreRequestHook loop and the pin-commit block that reads the routing pin.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@core/bifrost.go`:
- Around line 6854-6890: The staging routing pin
BifrostContextKeyRoutingPinnedAPIKeyID is left on a shared BifrostContext and
can leak between requests; fix by explicitly clearing/resetting that key
per-request (e.g., call
ctx.SetValue(schemas.BifrostContextKeyRoutingPinnedAPIKeyID, "") or remove it)
at the start of request handling (before running plugin PreRequestHook and
before any blocked/unblocked phases) and ensure the commit logic that reads the
pin (the block that calls ctx.SetValue(schemas.BifrostContextKeyAPIKeyID, pin))
treats an empty string as “no pin” so no stale value is re-applied; reference
symbols: BifrostContextKeyRoutingPinnedAPIKeyID, ctx.SetValue, PreRequestHook
loop and the pin-commit block that reads the routing pin.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 85e216d1-72d0-4c79-adb1-5e608a858f73
📒 Files selected for processing (5)
core/bifrost.gocore/bifrost_test.gocore/schemas/bifrost.goplugins/governance/main.goplugins/governance/routing_test.go
Merge activity
|
…yID` after `PreRequestHook` unblock (#4359) ## Summary Routing rules that pin a specific API key by ID were silently broken. During `PreRequestHook` execution, core blocks writes to reserved context keys (including `BifrostContextKeyAPIKeyID`) to prevent plugins from overriding caller-supplied values. The governance plugin was writing the routing-rule key pin directly to that reserved key, so the write was dropped and the pin never took effect. ## Changes - Introduced a new non-reserved context key `BifrostContextKeyRoutingPinnedAPIKeyID` that the governance plugin writes its routing-rule key pin to during the blocked `PreRequestHook` phase. - After all `PreRequestHook`s complete and the restricted-write block is lifted, core's `RunPreRequestHooks` commits the routing pin from `BifrostContextKeyRoutingPinnedAPIKeyID` into the reserved `BifrostContextKeyAPIKeyID`. A non-empty routing pin overrides any caller-supplied pin, since the routing rule represents authoritative server-side policy. - The `defer ctx.UnblockRestrictedWrites()` was replaced with an explicit call after the plugin loop, so the commit step runs after the block is lifted rather than after the function returns. - Updated the governance plugin's `applyRoutingRules` to write to the new non-reserved key instead of the reserved one. - Updated tests in both `core/bifrost_test.go` and `plugins/governance/routing_test.go` to exercise the real propagation path, including the restricted-write block and plugin scope, and to assert the pin lands on the correct context key. ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Plugins ## How to test ```sh go test ./core/... ./plugins/governance/... ``` The new `TestRunPreRequestHooks_CommitsRoutingPinnedKey` test covers three cases: 1. A routing pin is committed to the reserved `BifrostContextKeyAPIKeyID`. 2. A routing pin overrides a caller-supplied `BifrostContextKeyAPIKeyID`. 3. A caller-supplied `BifrostContextKeyAPIKeyID` is preserved when no routing pin is set. The updated `TestEvaluateRoutingRules_MultiTargetDeterministicWithPinnedKey` test exercises the full propagation path through `applyRoutingRules` under the same restricted-write block that production uses. ## Breaking changes - [ ] Yes - [x] No ## Security considerations The routing pin is written to a dedicated non-reserved key and committed to the reserved key exclusively by core after the plugin phase completes. This preserves the invariant that only core is the authoritative writer of `BifrostContextKeyAPIKeyID`, preventing plugins from directly overriding key selection outside of the sanctioned routing-rule mechanism. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Routing plugins can now pin specific API keys that take precedence over caller-supplied selections through dedicated routing-pinned key handling in the pre-request hook phase. * **Tests** * Added tests to verify routing-pinned API key commitment, precedence handling, and proper context propagation through key selection logic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…yID` after `PreRequestHook` unblock (#4359) ## Summary Routing rules that pin a specific API key by ID were silently broken. During `PreRequestHook` execution, core blocks writes to reserved context keys (including `BifrostContextKeyAPIKeyID`) to prevent plugins from overriding caller-supplied values. The governance plugin was writing the routing-rule key pin directly to that reserved key, so the write was dropped and the pin never took effect. ## Changes - Introduced a new non-reserved context key `BifrostContextKeyRoutingPinnedAPIKeyID` that the governance plugin writes its routing-rule key pin to during the blocked `PreRequestHook` phase. - After all `PreRequestHook`s complete and the restricted-write block is lifted, core's `RunPreRequestHooks` commits the routing pin from `BifrostContextKeyRoutingPinnedAPIKeyID` into the reserved `BifrostContextKeyAPIKeyID`. A non-empty routing pin overrides any caller-supplied pin, since the routing rule represents authoritative server-side policy. - The `defer ctx.UnblockRestrictedWrites()` was replaced with an explicit call after the plugin loop, so the commit step runs after the block is lifted rather than after the function returns. - Updated the governance plugin's `applyRoutingRules` to write to the new non-reserved key instead of the reserved one. - Updated tests in both `core/bifrost_test.go` and `plugins/governance/routing_test.go` to exercise the real propagation path, including the restricted-write block and plugin scope, and to assert the pin lands on the correct context key. ## Type of change - [x] Bug fix ## Affected areas - [x] Core (Go) - [x] Plugins ## How to test ```sh go test ./core/... ./plugins/governance/... ``` The new `TestRunPreRequestHooks_CommitsRoutingPinnedKey` test covers three cases: 1. A routing pin is committed to the reserved `BifrostContextKeyAPIKeyID`. 2. A routing pin overrides a caller-supplied `BifrostContextKeyAPIKeyID`. 3. A caller-supplied `BifrostContextKeyAPIKeyID` is preserved when no routing pin is set. The updated `TestEvaluateRoutingRules_MultiTargetDeterministicWithPinnedKey` test exercises the full propagation path through `applyRoutingRules` under the same restricted-write block that production uses. ## Breaking changes - [ ] Yes - [x] No ## Security considerations The routing pin is written to a dedicated non-reserved key and committed to the reserved key exclusively by core after the plugin phase completes. This preserves the invariant that only core is the authoritative writer of `BifrostContextKeyAPIKeyID`, preventing plugins from directly overriding key selection outside of the sanctioned routing-rule mechanism. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Routing plugins can now pin specific API keys that take precedence over caller-supplied selections through dedicated routing-pinned key handling in the pre-request hook phase. * **Tests** * Added tests to verify routing-pinned API key commitment, precedence handling, and proper context propagation through key selection logic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->

Summary
Routing rules that pin a specific API key by ID were silently broken. During
PreRequestHookexecution, core blocks writes to reserved context keys (includingBifrostContextKeyAPIKeyID) to prevent plugins from overriding caller-supplied values. The governance plugin was writing the routing-rule key pin directly to that reserved key, so the write was dropped and the pin never took effect.Changes
BifrostContextKeyRoutingPinnedAPIKeyIDthat the governance plugin writes its routing-rule key pin to during the blockedPreRequestHookphase.PreRequestHooks complete and the restricted-write block is lifted, core'sRunPreRequestHookscommits the routing pin fromBifrostContextKeyRoutingPinnedAPIKeyIDinto the reservedBifrostContextKeyAPIKeyID. A non-empty routing pin overrides any caller-supplied pin, since the routing rule represents authoritative server-side policy.defer ctx.UnblockRestrictedWrites()was replaced with an explicit call after the plugin loop, so the commit step runs after the block is lifted rather than after the function returns.applyRoutingRulesto write to the new non-reserved key instead of the reserved one.core/bifrost_test.goandplugins/governance/routing_test.goto exercise the real propagation path, including the restricted-write block and plugin scope, and to assert the pin lands on the correct context key.Type of change
Affected areas
How to test
go test ./core/... ./plugins/governance/...The new
TestRunPreRequestHooks_CommitsRoutingPinnedKeytest covers three cases:BifrostContextKeyAPIKeyID.BifrostContextKeyAPIKeyID.BifrostContextKeyAPIKeyIDis preserved when no routing pin is set.The updated
TestEvaluateRoutingRules_MultiTargetDeterministicWithPinnedKeytest exercises the full propagation path throughapplyRoutingRulesunder the same restricted-write block that production uses.Breaking changes
Security considerations
The routing pin is written to a dedicated non-reserved key and committed to the reserved key exclusively by core after the plugin phase completes. This preserves the invariant that only core is the authoritative writer of
BifrostContextKeyAPIKeyID, preventing plugins from directly overriding key selection outside of the sanctioned routing-rule mechanism.Checklist
docs/contributing/README.mdand followed the guidelinesSummary by CodeRabbit
Release Notes
New Features
Tests