Skip to content

exception fixes#2659

Merged
akshaydeo merged 1 commit intov1.5.0from
04-13-exception_fixes
Apr 13, 2026
Merged

exception fixes#2659
akshaydeo merged 1 commit intov1.5.0from
04-13-exception_fixes

Conversation

@akshaydeo
Copy link
Copy Markdown
Contributor

@akshaydeo akshaydeo commented Apr 12, 2026

Summary

Fixes a race condition in streaming response handling where transport post-hooks could access recycled fasthttp RequestCtx objects, leading to potential crashes or data corruption. The fix ensures thread-safe execution by capturing request/response data before goroutine execution and using atomic operations for coordination.

Changes

  • Modified trace completer signature to accept transport logs as parameters instead of reading from context
  • Implemented atomic.Value-based coordination between streaming handlers and transport interceptor middleware
  • Added BuildHTTPResponseFromFastHTTP function to create response snapshots
  • Created runTransportPostHooksCaptured function that operates on pre-captured data instead of live context
  • Updated streaming handlers to capture trace completers before goroutine execution
  • Enhanced context key documentation to clarify parameter signatures and usage patterns

The key design decision was to eagerly capture all needed data from the fasthttp context while it's still valid, then pass this data through closures to avoid any context access after the response stream begins.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

Test streaming endpoints with transport plugins enabled to verify no race conditions occur:

# Core/Transports
go version
go test ./...

# Test streaming responses with concurrent load
# Verify transport post-hooks execute without crashes
# Check that plugin logs are properly captured and forwarded to traces

Verify that transport plugin logs appear correctly in traces for both streaming and non-streaming responses.

Screenshots/Recordings

N/A - Internal concurrency fix with no UI changes.

Breaking changes

  • Yes
  • No

Related issues

Addresses race conditions in streaming response handling where fasthttp context recycling could cause crashes in transport post-hooks.

Security considerations

This fix prevents potential memory corruption and crashes that could occur due to race conditions, improving overall system stability and security.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4315ccd-b17e-4bc1-a5e1-09408cf5f4f9

📥 Commits

Reviewing files that changed from the base of the PR and between e614eb1 and 302f634.

⛔ Files ignored due to path filters (1)
  • plugins/prompts/go.sum is excluded by !**/*.sum
📒 Files selected for processing (27)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/providers/azure/azure.go
  • core/providers/mistral/custom_provider_test.go
  • core/providers/mistral/errors.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/ocr_test.go
  • core/schemas/bifrost.go
  • core/schemas/chatcompletions.go
  • framework/modelcatalog/config.go
  • framework/modelcatalog/main.go
  • framework/modelcatalog/pricing.go
  • framework/modelcatalog/pricing_test.go
  • framework/modelcatalog/utils.go
  • framework/oauth2/main.go
  • plugins/prompts/go.mod
  • transports/bifrost-http/handlers/asyncinference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/pricing_integration_test.go
  • ui/app/workspace/dashboard/page.tsx

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved streaming lifecycle to avoid recycled request contexts and made deferred post-hook error handling more robust.
    • Standardized response/error metadata to preserve original and resolved model identifiers.
  • Refactor

    • Reworked plugin log collection and trace completion to safely propagate transport logs across streaming boundaries.
    • Made transport post-hook handling safe for deferred/background execution.
  • New Features

    • Added a helper to capture HTTP response metadata (status and headers).
  • Chores

    • Renamed pricing sync/config keys and updated pricing model handling.

Walkthrough

Trace completion and transport post-hook handling were made goroutine-safe: trace completers now accept transport plugin logs, transport post-hook completers are stored in an *atomic.Value slot and run against captured request/response snapshots; added helper to build an HTTP response snapshot (status + headers). Other unrelated changes touch pricing, providers, and tests.

Changes

Cohort / File(s) Summary
Core schema/type updates
core/schemas/bifrost.go
Changed documented/context key value types: BifrostContextKeyTraceCompleterfunc([]PluginLogEntry); BifrostContextKeyTransportPostHookCompleter*atomic.Value storing func() ([]PluginLogEntry, error).
Streaming handlers & invocation updates
transports/bifrost-http/handlers/inference.go, transports/bifrost-http/handlers/asyncinference.go, transports/bifrost-http/integrations/router.go
Capture trace completer earlier with new signature func([]schemas.PluginLogEntry); producer goroutine loads post-hook completer from atomic slot and uses returned transport logs; pass MCP header combined allowlist into bifrost context conversion; minor formatting/tag tweaks.
Middleware: post-hook capture & execution
transports/bifrost-http/handlers/middlewares.go
Store an *atomic.Value slot with a captured completer that runs runTransportPostHooksCaptured (uses cloned snapshots, drains plugin logs, returns []PluginLogEntry, error); merge pre-hook and post-hook logs; update trace completion to accept transport logs directly.
Context utilities
transports/bifrost-http/lib/ctx.go
Added exported helper BuildHTTPResponseFromFastHTTP(ctx *fasthttp.RequestCtx) *schemas.HTTPResponse to snapshot response status and headers (body omitted).
Transport config / sync constants
transports/bifrost-http/lib/config.go, transports/bifrost-http/lib/config_test.go, transports/bifrost-http/lib/pricing_integration_test.go, transports/bifrost-http/handlers/config.go
Swapped usage to new naming DefaultSyncInterval / MinimumSyncIntervalSec and updated clamp logic/tests to reference those constants; minor whitespace/alignment edits.
Removed pricing override validation
transports/bifrost-http/handlers/providers.go
Deleted local pricing-override validation helpers and regex-based checks (validation logic removed).
Model catalog: pricing refactor & tests
framework/modelcatalog/pricing.go, framework/modelcatalog/main.go, framework/modelcatalog/config.go, framework/modelcatalog/pricing_test.go, framework/modelcatalog/utils.go
Reworked pricing types: added PricingEntry, PricingOptions, UnmarshalJSON handling for tiered inputs (sonic usage), introduced token-tier constants, renamed sync-interval constants, updated init/wiring and many tests to new structures and CalculateCost signature.
Providers: error/extra-fields cleanup
core/providers/mistral/errors.go, core/providers/mistral/mistral.go, core/providers/azure/azure.go, core/schemas/chatcompletions.go
Removed population of ModelRequested in many ExtraFields; switched to OriginalModelRequested / ResolvedModelUsed; simplified ParseMistralError signature and updated call sites to remove provider/model/request-type context.
Core logic & tests small changes
core/bifrost.go, core/bifrost_test.go, core/providers/mistral/*_test.go, core/providers/mistral/ocr_test.go
Adjusted early OCR validation/error extra fields to set OriginalModelRequested/ResolvedModelUsed; updated tests to assert on OriginalModelRequested.
OAuth2 and module changes
framework/oauth2/main.go, plugins/prompts/go.mod
Forward request ctx into token-exchange helpers; added indirect dependency github.com/jackc/pgx/v5 v5.9.1 in module file.
UI formatting/refactors
ui/app/workspace/dashboard/page.tsx
Code-style and formatting simplifications in data-fetching callbacks and JSX; no runtime behavior changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Router as Router
    participant Producer as ProducerGoroutine
    participant PostHooks as PostHookRunner
    participant Tracer as Tracer

    Client->>Router: open streaming request
    Router->>Producer: start producer goroutine (capture traceCompleter, store completerSlot)
    Producer->>Client: stream SSE chunks...
    Producer-->>Producer: defer cleanup -> load completer from completerSlot
    Producer->>PostHooks: invoke completer -> runTransportPostHooksCaptured(snapshot)
    PostHooks-->Producer: transportLogs or error
    Producer->>Tracer: call traceCompleter(transportLogs)
    Tracer-->>Producer: ack
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I snapped headers, left bodies behind,
Stored a completer safe for the grind.
Logs hop out when goroutines sleep,
Captured snapshots wake without a peep.
Hooray—no race, just tidy log-kind.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'exception fixes' is vague and generic, failing to communicate the specific nature of the changes (race condition in streaming response handling with fasthttp context recycling). Revise the title to be more specific, such as 'Fix race condition in streaming response handling with fasthttp context recycling' or 'Use atomic coordination for thread-safe transport post-hooks'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all required template sections including summary, changes, type of change, affected areas, testing instructions, and security considerations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 04-13-exception_fixes

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@akshaydeo akshaydeo marked this pull request as ready for review April 12, 2026 23:11
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
transports/bifrost-http/lib/ctx.go (1)

620-624: Clear reused response fields before building the snapshot.

This helper only fills part of a pooled schemas.HTTPResponse. Clearing any omitted fields up front makes the “status + headers only” contract independent of pool cleanup and avoids stale data bleeding into later hook consumers.

As per coding guidelines, "Always reset ALL fields of pooled objects before calling pool.Put() to prevent stale data from leaking to subsequent users."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/lib/ctx.go` around lines 620 - 624, The pooled resp
returned by schemas.AcquireHTTPResponse() must be fully reset before populating
just status and headers to avoid leaking stale pooled data; update the code
around resp (the schemas.AcquireHTTPResponse() result) to zero-out or
reinitialize all fields on the HTTPResponse struct (e.g., clear Body, Trailer,
Cookies, any Proto/Reason/Flags, and replace or clear the Headers map) and then
set resp.StatusCode and copy ctx.Response.Header into resp.Headers so the
snapshot contains only the intended fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 364-390: The deferred completer is being stored into the atomic
slot after next(ctx) returns, allowing a racing goroutine to load a nil/absent
value and leak capturedReq/capturedResp; move the atomic publish so the slot is
populated before the handler returns. Specifically, locate the
ctx.UserValue(schemas.BifrostContextKeyTransportPostHookCompleter) slot and call
slot.Store(completer) (or otherwise publish the completer) prior to invoking
next(ctx) (or any point before the handler can return), ensuring the
closure-created completer (which wraps capturedReq/capturedResp and calls
runTransportPostHooksCaptured) is visible atomically to the cleanup goroutine to
avoid dropped post-hooks and leaked snapshots.
- Around line 462-506: The cloned request used in runTransportPostHooksCaptured
omits copying capturedReq.Body so post-hooks see wrong payloads for streamed
requests; fix by copying the body from capturedReq into the pooled req (e.g., if
capturedReq.Body != nil create a deep copy/byte buffer and assign it to
req.Body/any related body fields) so plugins operate on the same payload as the
captured snapshot (ensure you copy, not alias, so plugins can mutate req
safely).

---

Nitpick comments:
In `@transports/bifrost-http/lib/ctx.go`:
- Around line 620-624: The pooled resp returned by schemas.AcquireHTTPResponse()
must be fully reset before populating just status and headers to avoid leaking
stale pooled data; update the code around resp (the
schemas.AcquireHTTPResponse() result) to zero-out or reinitialize all fields on
the HTTPResponse struct (e.g., clear Body, Trailer, Cookies, any
Proto/Reason/Flags, and replace or clear the Headers map) and then set
resp.StatusCode and copy ctx.Response.Header into resp.Headers so the snapshot
contains only the intended fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 53cdba1f-023a-46fa-8447-55ac62ebbb32

📥 Commits

Reviewing files that changed from the base of the PR and between 940085c and 83b7999.

📒 Files selected for processing (5)
  • core/schemas/bifrost.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/ctx.go

Comment thread transports/bifrost-http/handlers/middlewares.go
Comment thread transports/bifrost-http/handlers/middlewares.go
@akshaydeo akshaydeo force-pushed the 04-13-exception_fixes branch from 83b7999 to d8fe420 Compare April 12, 2026 23:34
@coderabbitai coderabbitai Bot requested a review from danpiths April 12, 2026 23:35
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/providers/azure/azure.go (1)

2864-2909: ⚠️ Potential issue | 🔴 Critical

Capture the post-hook finalizer before starting the goroutine.

The EOF path still does a late ctx.Value(schemas.BifrostContextKeyPostHookSpanFinalizer) lookup and invokes hooks from inside the stream goroutine. That can preserve the same lifetime bug this stack is fixing if the stored closure still closes over transport-scoped state. Resolve the finalizer/captured hook once before go func() and pass the captured function(s) into the goroutine instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/providers/azure/azure.go` around lines 2864 - 2909, The EOF path
currently looks up and invokes the post-hook finalizer inside the streaming
goroutine, which can keep transport-scoped state alive; fix this by reading and
storing the finalizer (ctx.Value(schemas.BifrostContextKeyPostHookSpanFinalizer)
cast to func(context.Context)) into a local variable before starting the
goroutine and passing that captured finalizer into the goroutine; then, in the
EOF handling path inside the loop (where postHookRunner is called and the
finalizer is invoked), call the pre-captured finalizer if not nil instead of
doing a fresh ctx.Value lookup, ensuring you still call postHookRunner(ctx,
finalResp, nil) and invoke the captured finalizer(ctx) when present.
transports/bifrost-http/integrations/router.go (1)

2317-2343: ⚠️ Potential issue | 🟠 Major

Capture and run the transport post-hook completer here too.

This path now captures traceCompleter safely, but it still hardcodes traceCompleter(nil) and never loads BifrostContextKeyTransportPostHookCompleter. Since RegisterRoutes runs these handlers through the shared middleware stack, streaming integration routes can skip the captured transport post-hooks or lose their plugin logs in the final trace. Please mirror the transports/bifrost-http/handlers/inference.go cleanup flow here: capture the atomic completer before the goroutine starts, run it in the deferred teardown, and pass the returned logs into traceCompleter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/integrations/router.go` around lines 2317 - 2343,
Capture the transport post-hook completer from context (using
schemas.BifrostContextKeyTransportPostHookCompleter) before starting the
goroutine (just like traceCompleter is captured) and store it (e.g.,
transportPostHookCompleter). In the deferred teardown inside the producer
goroutine (where reader.Done() and schemas.ReleaseHTTPRequest(httpReq) are
deferred), call the transportPostHookCompleter (if non-nil) to obtain
transportLogs and then pass those logs into traceCompleter(transportLogs)
instead of traceCompleter(nil); ensure both completers are nil-checked and
reference the existing symbols used here (traceCompleter,
NewSSEStreamReader/reader, BuildHTTPRequestFromFastHTTP,
GetStreamChunkInterceptor, schemas.ReleaseHTTPRequest) so the change mirrors the
cleanup flow in transports/bifrost-http/handlers/inference.go.
♻️ Duplicate comments (1)
transports/bifrost-http/handlers/middlewares.go (1)

462-476: ⚠️ Potential issue | 🟠 Major

Request body is still not copied to the cloned request object.

The past review comment on this section remains unaddressed. The clone copies Method, Path, Headers, Query, and PathParams from capturedReq, but omits Body. Any transport post-hook that logs, redacts, or validates the request payload will see an empty body for streaming requests.

🐛 Proposed fix to copy the body
 	req.Method = capturedReq.Method
 	req.Path = capturedReq.Path
+	if len(capturedReq.Body) > 0 {
+		req.Body = make([]byte, len(capturedReq.Body))
+		copy(req.Body, capturedReq.Body)
+	}
 	for k, v := range capturedReq.Headers {
 		req.Headers[k] = v
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares.go` around lines 462 - 476, In
runTransportPostHooksCaptured the cloned request omits the request body so
plugins see an empty payload for streaming requests; copy and deep-clone
capturedReq.Body into the pooled req (e.g., set req.Body to a new byte slice
copy of capturedReq.Body) before returning to ensure plugins can read/mutate
safely (refer to runTransportPostHooksCaptured, schemas.AcquireHTTPRequest, and
the schemas.HTTPRequest.Body field).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/bifrost.go`:
- Around line 1029-1030: Validation branches that return early for OCR requests
are not attaching model context, so populate OriginalModelRequested and
ResolvedModelUsed on the returned error responses; in the branches that build
the response for schemas.OCRRequest (where RequestType: schemas.OCRRequest and
Provider: req.Provider are set) add OriginalModelRequested:
req.OriginalModelRequested and ResolvedModelUsed: req.ResolvedModelUsed (or the
resolved model variable) so all three early-return error paths include the same
model metadata as other validators.

In `@core/providers/azure/azure.go`:
- Around line 2854-2857: The passthrough stream response metadata currently
constructs extraFields without preserving the requested model; update the
construction of schemas.BifrostResponseExtraFields (the extraFields variable
created near provider.GetProviderKey() and schemas.PassthroughStreamRequest) to
include OriginalModelRequested set from the incoming request model (e.g.,
req.Model or the function's request parameter) so chunk/final-response hooks and
traces retain the requested model information.

In `@core/providers/mistral/errors.go`:
- Around line 22-24: ParseMistralError currently drops provider/request
metadata; restore the metadata inputs or make the helper internal. Specifically,
either revert ParseMistralError to accept the metadata parameters (e.g.,
provider string, requestType string, originalModelRequested string) and populate
bifrostErr.ExtraFields.Provider, .RequestType and .OriginalModelRequested before
returning, or mark ParseMistralError unexported and update tests to use
core/bifrost.go's PopulateExtraFields path; reference the ParseMistralError
function and (*schemas.BifrostError).PopulateExtraFields to ensure the metadata
is set consistently.

In `@core/schemas/chatcompletions.go`:
- Around line 165-172: The conversion in ToTextCompletionResponse() drops model
metadata—add OriginalModelRequested and ResolvedModelUsed to the
BifrostResponseExtraFields struct literal so they are copied from cr.ExtraFields
(e.g., ExtraFields: BifrostResponseExtraFields{ RequestType: ..., ChunkIndex:
cr.ExtraFields.ChunkIndex, Provider: cr.ExtraFields.Provider, Latency:
cr.ExtraFields.Latency, RawResponse: cr.ExtraFields.RawResponse, CacheDebug:
cr.ExtraFields.CacheDebug, OriginalModelRequested:
cr.ExtraFields.OriginalModelRequested, ResolvedModelUsed:
cr.ExtraFields.ResolvedModelUsed }). Make the same addition in every return
branch inside ToTextCompletionResponse() so the model identity is preserved
across all code paths.

---

Outside diff comments:
In `@core/providers/azure/azure.go`:
- Around line 2864-2909: The EOF path currently looks up and invokes the
post-hook finalizer inside the streaming goroutine, which can keep
transport-scoped state alive; fix this by reading and storing the finalizer
(ctx.Value(schemas.BifrostContextKeyPostHookSpanFinalizer) cast to
func(context.Context)) into a local variable before starting the goroutine and
passing that captured finalizer into the goroutine; then, in the EOF handling
path inside the loop (where postHookRunner is called and the finalizer is
invoked), call the pre-captured finalizer if not nil instead of doing a fresh
ctx.Value lookup, ensuring you still call postHookRunner(ctx, finalResp, nil)
and invoke the captured finalizer(ctx) when present.

In `@transports/bifrost-http/integrations/router.go`:
- Around line 2317-2343: Capture the transport post-hook completer from context
(using schemas.BifrostContextKeyTransportPostHookCompleter) before starting the
goroutine (just like traceCompleter is captured) and store it (e.g.,
transportPostHookCompleter). In the deferred teardown inside the producer
goroutine (where reader.Done() and schemas.ReleaseHTTPRequest(httpReq) are
deferred), call the transportPostHookCompleter (if non-nil) to obtain
transportLogs and then pass those logs into traceCompleter(transportLogs)
instead of traceCompleter(nil); ensure both completers are nil-checked and
reference the existing symbols used here (traceCompleter,
NewSSEStreamReader/reader, BuildHTTPRequestFromFastHTTP,
GetStreamChunkInterceptor, schemas.ReleaseHTTPRequest) so the change mirrors the
cleanup flow in transports/bifrost-http/handlers/inference.go.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 462-476: In runTransportPostHooksCaptured the cloned request omits
the request body so plugins see an empty payload for streaming requests; copy
and deep-clone capturedReq.Body into the pooled req (e.g., set req.Body to a new
byte slice copy of capturedReq.Body) before returning to ensure plugins can
read/mutate safely (refer to runTransportPostHooksCaptured,
schemas.AcquireHTTPRequest, and the schemas.HTTPRequest.Body field).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 030d94a0-93a5-4327-a412-c113a6b7d095

📥 Commits

Reviewing files that changed from the base of the PR and between 83b7999 and d8fe420.

📒 Files selected for processing (13)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/providers/azure/azure.go
  • core/providers/mistral/custom_provider_test.go
  • core/providers/mistral/errors.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/ocr_test.go
  • core/schemas/bifrost.go
  • core/schemas/chatcompletions.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/ctx.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go

Comment thread core/bifrost.go Outdated
Comment thread core/providers/azure/azure.go Outdated
Comment thread core/providers/mistral/errors.go
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 12, 2026

Confidence Score: 4/5

The core streaming race-condition fix is sound, but one streaming path silently drops traces and should be addressed before merging.

The inference.go and handleStreaming paths are correctly implemented. One P1 gap remains: handlePassthroughStream sets the deferred-trace flag but never captures or calls traceCompleter, causing traces for all passthrough streaming requests to be silently dropped. Resolving the passthrough trace gap is a one-line capture plus one-line call that mirrors the already-correct handleStreaming pattern.

transports/bifrost-http/integrations/router.go — handlePassthroughStream goroutine is missing traceCompleter capture and call

Important Files Changed

Filename Overview
transports/bifrost-http/integrations/router.go handleStreaming correctly captures/calls traceCompleter(nil), but handlePassthroughStream sets DeferTraceCompletion=true without ever calling traceCompleter, so traces for passthrough streaming are silently dropped.
transports/bifrost-http/handlers/middlewares.go TransportInterceptorMiddleware eagerly snapshots req/resp and creates the deferred completer closure correctly; runTransportPostHooksCaptured is a clean ctx-free post-hook runner.
transports/bifrost-http/handlers/inference.go handleStreamingResponse correctly captures traceCompleter and completerSlot before the goroutine, and the goroutine's defer sequentially runs transport post-hooks then completes the trace.
transports/bifrost-http/lib/ctx.go BuildHTTPRequestFromFastHTTP and BuildHTTPResponseFromFastHTTP correctly snapshot request/response data from fasthttp context for use after ctx recycling; body intentionally omitted for streaming.
core/schemas/bifrost.go Context key documentation updated to clarify traceCompleter and transportPostHookCompleter signatures; no logic changes.
core/bifrost.go Minor updates unrelated to the streaming fix; no issues found.

Comments Outside Diff (1)

  1. transports/bifrost-http/integrations/router.go, line 2803-2824 (link)

    P1 Passthrough streaming trace is never completed

    This handler sets the deferred-trace-completion flag (line 2804), which causes TracingMiddleware to skip its inline CompleteAndFlushTrace call. But the goroutine never captures or invokes traceCompleter. For every passthrough streaming request with tracing enabled, spans are opened and never closed — trace data is silently dropped and never flushed to the OTEL backend.

    The non-passthrough handleStreaming function in this same file (lines 2315–2344) shows the correct pattern: capture traceCompleter from ctx before the goroutine starts (since ctx may be recycled once the goroutine runs), then call it with nil transport logs inside the goroutine's defer. The passthrough goroutine is missing this step.

Reviews (5): Last reviewed commit: "exception fixes" | Re-trigger Greptile

Comment thread transports/bifrost-http/handlers/middlewares.go
Comment thread transports/bifrost-http/handlers/inference.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
transports/bifrost-http/integrations/router.go (1)

2317-2343: ⚠️ Potential issue | 🟠 Major

Install and execute the deferred transport post-hook completer here too.

This streaming path only captures traceCompleter, but it never creates/loads schemas.BifrostContextKeyTransportPostHookCompleter. Because these routes still run the shared middleware chain, TransportInterceptorMiddleware will skip the deferred post-hooks unless that *atomic.Value slot exists. The result is that streaming integration routes lose transport post-hooks entirely, traces always miss transport plugin logs, and the captured request/response snapshots in transports/bifrost-http/handlers/middlewares.go:355-395 never get released through the completer path. transports/bifrost-http/handlers/inference.go:1676-1720 already has the correct pattern; this handler needs the same handoff/load flow instead of hard-coding traceCompleter(nil).

Suggested fix
+// also add `sync/atomic` to the imports
+
 	// Capture trace completer BEFORE goroutine — ctx may be recycled inside goroutine.
 	// Signature: func(transportLogs []schemas.PluginLogEntry)
+	var completerSlot atomic.Value
+	ctx.SetUserValue(schemas.BifrostContextKeyTransportPostHookCompleter, &completerSlot)
 	traceCompleter, _ := ctx.UserValue(schemas.BifrostContextKeyTraceCompleter).(func([]schemas.PluginLogEntry))
@@
 		defer func() {
-			// Complete the trace after streaming finishes
-			// This ensures all spans (including llm.call) are properly ended before the trace is sent to OTEL.
-			// Router path has no transport post-hooks, so pass nil for transport logs.
+			var transportLogs []schemas.PluginLogEntry
+			if fn, ok := completerSlot.Load().(func() ([]schemas.PluginLogEntry, error)); ok && fn != nil {
+				logs, err := fn()
+				transportLogs = logs
+				if err != nil {
+					g.logger.Warn("transport post-hooks failed: %v", err)
+				}
+			}
 			if traceCompleter != nil {
-				traceCompleter(nil)
+				traceCompleter(transportLogs)
 			}
 		}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/integrations/router.go` around lines 2317 - 2343, The
streaming handler currently only captures traceCompleter but never loads or
invokes the transport post-hook completer, so transport post-hooks and plugin
logs are skipped; fix this by reading
schemas.BifrostContextKeyTransportPostHookCompleter from the ctx (same pattern
as TransportInterceptorMiddleware / the inference handler): capture a local
variable like transportCompleterVal, check it != nil, do v :=
transportCompleterVal.(*atomic.Value) and if v.Load() != nil cast to a
func([]schemas.PluginLogEntry) and call that completer (passing collected
transport plugin logs or nil if none) in the same deferred cleanup block
before/alongside traceCompleter(nil) so transport post-hooks are executed and
logs are handed off correctly.
🧹 Nitpick comments (1)
framework/modelcatalog/pricing_test.go (1)

193-196: Inconsistent pointer creation style.

The file now mixes bifrost.Ptr(...) (e.g., lines 23-24, 132-133) with new(float64_literal) (lines 193-196, 213-216, 318, etc.). While both are valid, consider unifying to bifrost.Ptr() for consistency with repository conventions.

Based on learnings: "prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically. Apply this consistently across all code paths."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/modelcatalog/pricing_test.go` around lines 193 - 196, The test uses
raw new(float64) pointer creation for pricing fields (e.g.,
p.InputCostPerTokenAbove200kTokens, p.OutputCostPerTokenAbove200kTokens,
p.InputCostPerTokenAbove272kTokens, p.OutputCostPerTokenAbove272kTokens);
replace these with the repository-convention helper bifrost.Ptr(float64) so all
pointer float literals in pricing_test.go follow the existing bifrost.Ptr(...)
style (match other instances around lines where bifrost.Ptr is already used).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@transports/bifrost-http/lib/pricing_integration_test.go`:
- Around line 514-516: The test calls mc.SetShouldSyncGate immediately after
modelcatalog.Init which can dereference mc if Init returned an error; change the
order so you assert require.NoError(t, err) right after calling
modelcatalog.Init(ctx, cfg, store, clg) and only then call
mc.SetShouldSyncGate(noSyncFunc). Apply the same change for all occurrences that
call modelcatalog.Init followed by mc.SetShouldSyncGate (the instances around
Init lines referenced) so Init error is checked before using mc.

---

Outside diff comments:
In `@transports/bifrost-http/integrations/router.go`:
- Around line 2317-2343: The streaming handler currently only captures
traceCompleter but never loads or invokes the transport post-hook completer, so
transport post-hooks and plugin logs are skipped; fix this by reading
schemas.BifrostContextKeyTransportPostHookCompleter from the ctx (same pattern
as TransportInterceptorMiddleware / the inference handler): capture a local
variable like transportCompleterVal, check it != nil, do v :=
transportCompleterVal.(*atomic.Value) and if v.Load() != nil cast to a
func([]schemas.PluginLogEntry) and call that completer (passing collected
transport plugin logs or nil if none) in the same deferred cleanup block
before/alongside traceCompleter(nil) so transport post-hooks are executed and
logs are handed off correctly.

---

Nitpick comments:
In `@framework/modelcatalog/pricing_test.go`:
- Around line 193-196: The test uses raw new(float64) pointer creation for
pricing fields (e.g., p.InputCostPerTokenAbove200kTokens,
p.OutputCostPerTokenAbove200kTokens, p.InputCostPerTokenAbove272kTokens,
p.OutputCostPerTokenAbove272kTokens); replace these with the
repository-convention helper bifrost.Ptr(float64) so all pointer float literals
in pricing_test.go follow the existing bifrost.Ptr(...) style (match other
instances around lines where bifrost.Ptr is already used).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa23ee03-b25a-4ca1-a76e-3b746006420f

📥 Commits

Reviewing files that changed from the base of the PR and between d8fe420 and 8b370cb.

⛔ Files ignored due to path filters (1)
  • plugins/prompts/go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/providers/azure/azure.go
  • core/providers/mistral/custom_provider_test.go
  • core/providers/mistral/errors.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/ocr_test.go
  • core/schemas/bifrost.go
  • core/schemas/chatcompletions.go
  • framework/modelcatalog/config.go
  • framework/modelcatalog/main.go
  • framework/modelcatalog/pricing.go
  • framework/modelcatalog/pricing_test.go
  • framework/modelcatalog/utils.go
  • framework/oauth2/main.go
  • plugins/prompts/go.mod
  • transports/bifrost-http/handlers/asyncinference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/pricing_integration_test.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/providers.go
✅ Files skipped from review due to trivial changes (4)
  • plugins/prompts/go.mod
  • framework/modelcatalog/utils.go
  • core/providers/mistral/ocr_test.go
  • core/bifrost.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • core/schemas/chatcompletions.go
  • core/providers/mistral/errors.go
  • core/providers/mistral/mistral.go
  • transports/bifrost-http/lib/ctx.go
  • core/providers/azure/azure.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/inference.go

Comment thread transports/bifrost-http/lib/pricing_integration_test.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
transports/bifrost-http/integrations/router.go (1)

2317-2343: ⚠️ Potential issue | 🟠 Major

Integration streaming still skips deferred transport post-hooks.

This cleanup only captures BifrostContextKeyTraceCompleter. Because these routes run through the shared middleware chain, TransportInterceptorMiddleware will switch to deferred mode for streamed responses, but there is no BifrostContextKeyTransportPostHookCompleter slot here to publish/load. That means streamed integration routes never run transport post-hooks, and their transport plugin logs never reach tracing when transport plugins are enabled.

Please mirror the handlers/inference.go pattern here: create/capture the post-hook completer slot before spawning the goroutine, run it in the deferred cleanup, and pass the returned logs into traceCompleter(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/integrations/router.go` around lines 2317 - 2343, The
streaming producer currently only captures BifrostContextKeyTraceCompleter but
misses the transport post-hook completer, so deferred transport post-hooks never
run for streamed routes; before spawning the goroutine capture the post-hook
completer from ctx (e.g., via
ctx.UserValue(schemas.BifrostContextKeyTransportPostHookCompleter).(func()
[]schemas.PluginLogEntry) or similar) into a variable (name it
transportPostHookCompleter), then in the goroutine's deferred cleanup call that
completer to obtain transport logs and pass those logs into the existing
traceCompleter(nil) call (i.e., call traceCompleter(transportLogs)), ensuring
you still call reader.Done() and schemas.ReleaseHTTPRequest(httpReq) in their
defers and preserve LIFO order.
♻️ Duplicate comments (1)
transports/bifrost-http/handlers/middlewares.go (1)

462-476: ⚠️ Potential issue | 🟠 Major

Copy capturedReq.Body into the cloned request.

runTransportPostHooksCaptured() still rebuilds the mutable request without the body, so streamed post-hooks see a different request than non-streaming ones. Any plugin that logs, redacts, or validates payloads will silently lose request content on streamed routes.

Proposed fix
 	req.Method = capturedReq.Method
 	req.Path = capturedReq.Path
+	if len(capturedReq.Body) > 0 {
+		req.Body = append(req.Body[:0], capturedReq.Body...)
+	}
 	for k, v := range capturedReq.Headers {
 		req.Headers[k] = v
 	}
 	for k, v := range capturedReq.Query {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares.go` around lines 462 - 476, In
runTransportPostHooksCaptured, the cloned mutable request (req acquired via
schemas.AcquireHTTPRequest) is missing the body from capturedReq, causing
streamed post-hooks to see an empty payload; update the function to copy
capturedReq.Body into req.Body (making a deep copy of the byte slice or
otherwise cloning the buffer) after copying headers/query/path params so plugins
that log/redact/validate payloads see the same request content as non-streamed
routes; ensure you use the same request variable (req) and release semantics
already present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@transports/bifrost-http/integrations/router.go`:
- Around line 2317-2343: The streaming producer currently only captures
BifrostContextKeyTraceCompleter but misses the transport post-hook completer, so
deferred transport post-hooks never run for streamed routes; before spawning the
goroutine capture the post-hook completer from ctx (e.g., via
ctx.UserValue(schemas.BifrostContextKeyTransportPostHookCompleter).(func()
[]schemas.PluginLogEntry) or similar) into a variable (name it
transportPostHookCompleter), then in the goroutine's deferred cleanup call that
completer to obtain transport logs and pass those logs into the existing
traceCompleter(nil) call (i.e., call traceCompleter(transportLogs)), ensuring
you still call reader.Done() and schemas.ReleaseHTTPRequest(httpReq) in their
defers and preserve LIFO order.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 462-476: In runTransportPostHooksCaptured, the cloned mutable
request (req acquired via schemas.AcquireHTTPRequest) is missing the body from
capturedReq, causing streamed post-hooks to see an empty payload; update the
function to copy capturedReq.Body into req.Body (making a deep copy of the byte
slice or otherwise cloning the buffer) after copying headers/query/path params
so plugins that log/redact/validate payloads see the same request content as
non-streamed routes; ensure you use the same request variable (req) and release
semantics already present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 753b7b91-784e-4d1f-ae25-c98dd7eeee6a

📥 Commits

Reviewing files that changed from the base of the PR and between 8b370cb and 82b2d6c.

⛔ Files ignored due to path filters (1)
  • plugins/prompts/go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/providers/azure/azure.go
  • core/providers/mistral/custom_provider_test.go
  • core/providers/mistral/errors.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/ocr_test.go
  • core/schemas/bifrost.go
  • core/schemas/chatcompletions.go
  • framework/modelcatalog/config.go
  • framework/modelcatalog/main.go
  • framework/modelcatalog/pricing.go
  • framework/modelcatalog/pricing_test.go
  • framework/modelcatalog/utils.go
  • framework/oauth2/main.go
  • plugins/prompts/go.mod
  • transports/bifrost-http/handlers/asyncinference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/pricing_integration_test.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/providers.go
✅ Files skipped from review due to trivial changes (5)
  • plugins/prompts/go.mod
  • core/providers/mistral/ocr_test.go
  • framework/modelcatalog/config.go
  • framework/modelcatalog/utils.go
  • transports/bifrost-http/lib/config_test.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/asyncinference.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/custom_provider_test.go
  • transports/bifrost-http/lib/config.go
  • core/schemas/chatcompletions.go
  • transports/bifrost-http/handlers/inference.go
  • framework/modelcatalog/pricing_test.go
  • transports/bifrost-http/lib/pricing_integration_test.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/providers/azure/azure.go (1)

2844-2895: ⚠️ Potential issue | 🔴 Critical

Normal EOF still bypasses the captured post-hook path.

The timeout/error branches now use the safe helpers, but the success path still calls postHookRunner(ctx, finalResp, nil) directly and reads schemas.BifrostContextKeyPostHookSpanFinalizer from ctx inside the goroutine. That leaves normal passthrough completion exposed to the same recycled-request-context race this PR is fixing. Please route the EOF branch through the same captured post-hook/finalizer flow before spawning the goroutine.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/providers/azure/azure.go` around lines 2844 - 2895, The EOF success path
is still directly calling postHookRunner and reading the finalizer from ctx
inside the goroutine, exposing a recycled-context race; before launching the
goroutine capture the postHookRunner and the post-hook finalizer (e.g., read
provider.postHookRunner into a local like capturedPostHookRunner and extract
ctx.Value(schemas.BifrostContextKeyPostHookSpanFinalizer) into
capturedFinalizer) and then inside the goroutine use those
capturedPostHookRunner(capturedCtx, finalResp, nil) and
capturedFinalizer(capturedCtx) (if non-nil) for the EOF branch instead of
accessing ctx and postHookRunner directly so the EOF path uses the same safe
captured post-hook/finalizer flow as the timeout/error branches.
♻️ Duplicate comments (1)
transports/bifrost-http/handlers/middlewares.go (1)

463-476: ⚠️ Potential issue | 🟠 Major

Copy the captured request body into the cloned request.

The captured path still clones method/path/headers/query/path params only. capturedReq.Body never makes it into req, so streamed post-hooks see an empty payload while the non-streaming path passes the request body through.

Possible fix
 	req.Method = capturedReq.Method
 	req.Path = capturedReq.Path
+	if len(capturedReq.Body) > 0 {
+		req.Body = append(req.Body[:0], capturedReq.Body...)
+	}
 	for k, v := range capturedReq.Headers {
 		req.Headers[k] = v
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares.go` around lines 463 - 476, The
cloned request misses copying the body, so streamed post-hooks see no payload;
when creating req from capturedReq (in the AcquireHTTPRequest/ReleaseHTTPRequest
block), copy the body into req using a deep copy rather than sharing the
underlying buffer: if capturedReq.Body is a []byte set req.Body =
append([]byte(nil), capturedReq.Body...), or if it's an io.ReadCloser wrap a new
reader (e.g., ioutil.NopCloser(bytes.NewReader(...))) so the cloned req.Body
contains the same bytes and can be consumed independently; update the cloning
code around req, capturedReq, AcquireHTTPRequest and ReleaseHTTPRequest to
perform this deep copy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/schemas/chatcompletions.go`:
- Around line 171-180: The fallback branch constructing
BifrostResponseExtraFields in ToTextCompletionResponse (or the function building
the TextCompletionResponse) omits ProviderResponseHeaders, causing loss of
request-id and rate-limit metadata; update the struct literal in that branch to
include ProviderResponseHeaders: cr.ExtraFields.ProviderResponseHeaders so the
fallback path preserves headers just like the other return paths (look for the
ExtraFields: BifrostResponseExtraFields{...} block and add the
ProviderResponseHeaders field).

In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 365-390: The capturedReq and capturedResp snapshots are only
released inside the completer closure, so if
ctx.UserValue(schemas.BifrostContextKeyTransportPostHookCompleter) is missing or
the type assertion fails the snapshots leak; update the branch after the type
assertion on slot to release the pooled snapshots (call
schemas.ReleaseHTTPRequest(capturedReq) and
schemas.ReleaseHTTPResponse(capturedResp)) when ok == false and avoid silently
dropping post-hooks (optionally invoke
runTransportPostHooksCaptured(capturedReq, capturedResp, plugins, bifrostCtx)
synchronously or log the missing slot) so resources are cleaned up and post-hook
results are not lost.

---

Outside diff comments:
In `@core/providers/azure/azure.go`:
- Around line 2844-2895: The EOF success path is still directly calling
postHookRunner and reading the finalizer from ctx inside the goroutine, exposing
a recycled-context race; before launching the goroutine capture the
postHookRunner and the post-hook finalizer (e.g., read provider.postHookRunner
into a local like capturedPostHookRunner and extract
ctx.Value(schemas.BifrostContextKeyPostHookSpanFinalizer) into
capturedFinalizer) and then inside the goroutine use those
capturedPostHookRunner(capturedCtx, finalResp, nil) and
capturedFinalizer(capturedCtx) (if non-nil) for the EOF branch instead of
accessing ctx and postHookRunner directly so the EOF path uses the same safe
captured post-hook/finalizer flow as the timeout/error branches.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 463-476: The cloned request misses copying the body, so streamed
post-hooks see no payload; when creating req from capturedReq (in the
AcquireHTTPRequest/ReleaseHTTPRequest block), copy the body into req using a
deep copy rather than sharing the underlying buffer: if capturedReq.Body is a
[]byte set req.Body = append([]byte(nil), capturedReq.Body...), or if it's an
io.ReadCloser wrap a new reader (e.g., ioutil.NopCloser(bytes.NewReader(...)))
so the cloned req.Body contains the same bytes and can be consumed
independently; update the cloning code around req, capturedReq,
AcquireHTTPRequest and ReleaseHTTPRequest to perform this deep copy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 13223da3-92f9-4815-b7f7-7878d0fbc74d

📥 Commits

Reviewing files that changed from the base of the PR and between 82b2d6c and 36cf4ba.

⛔ Files ignored due to path filters (1)
  • plugins/prompts/go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/providers/azure/azure.go
  • core/providers/mistral/custom_provider_test.go
  • core/providers/mistral/errors.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/ocr_test.go
  • core/schemas/bifrost.go
  • core/schemas/chatcompletions.go
  • framework/modelcatalog/config.go
  • framework/modelcatalog/main.go
  • framework/modelcatalog/pricing.go
  • framework/modelcatalog/pricing_test.go
  • framework/modelcatalog/utils.go
  • framework/oauth2/main.go
  • plugins/prompts/go.mod
  • transports/bifrost-http/handlers/asyncinference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/pricing_integration_test.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/providers.go
✅ Files skipped from review due to trivial changes (7)
  • framework/modelcatalog/utils.go
  • framework/oauth2/main.go
  • core/providers/mistral/ocr_test.go
  • framework/modelcatalog/config.go
  • core/bifrost.go
  • plugins/prompts/go.mod
  • transports/bifrost-http/lib/config_test.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/config.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/custom_provider_test.go
  • core/providers/mistral/errors.go
  • framework/modelcatalog/pricing_test.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • core/schemas/bifrost.go
  • transports/bifrost-http/handlers/inference.go
  • framework/modelcatalog/pricing.go
  • transports/bifrost-http/integrations/router.go

Comment thread core/schemas/chatcompletions.go
Comment thread transports/bifrost-http/handlers/middlewares.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
transports/bifrost-http/handlers/middlewares.go (2)

462-476: ⚠️ Potential issue | 🟠 Major

Clone the captured request body for deferred post-hooks.

This clone preserves method/path/headers/query/path params, but req.Body stays empty. Any transport post-hook that logs, redacts, or validates the payload will see different input for streamed requests than for non-streamed ones.

Possible fix
 req.Method = capturedReq.Method
 req.Path = capturedReq.Path
+if len(capturedReq.Body) > 0 {
+	req.Body = append(req.Body[:0], capturedReq.Body...)
+}
 for k, v := range capturedReq.Headers {
 	req.Headers[k] = v
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares.go` around lines 462 - 476,
runTransportPostHooksCaptured clones headers/query/pathparams but doesn't copy
the captured request payload, so deferred post-hooks see an empty req.Body for
streamed requests; update the function (runTransportPostHooksCaptured) to
deep-copy capturedReq.Body into the pooled req.Body (preserving the same
type/contents as capturedReq.Body) when acquiring the new schemas.HTTPRequest so
post-hook plugins read the original payload (use a defensive copy or appropriate
clone for the body representation used in capturedReq.Body to avoid
sharing/mutation).

364-390: ⚠️ Potential issue | 🟠 Major

Synchronize the deferred completer handoff.

next(ctx) returns only after the handler has already started the producer goroutine. If the upstream stream closes immediately, that goroutine can hit its deferred Load() before slot.Store(completer) happens here, so transport post-hooks/logs are skipped and the captured snapshots never get released. Please add a regression test with an immediately-closing stream and a transport post-hook to verify this path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/middlewares.go` around lines 364 - 390, The
deferred completer can be raced by the producer goroutine; to fix, ensure the
completer is published to the atomic slot before the upstream goroutine can
Load() it: create and Store a placeholder (or Store the actual completer) into
the ctx UserValue slot keyed by BifrostContextKeyTransportPostHookCompleter
immediately after constructing completer (use slot.Store(completer) before
returning from the middleware or before calling next(ctx) where applicable), and
keep the existing capturedReq/capturedResp release in the completer; update
runTransportPostHooksCaptured usage and ensure slot.Store is used (atomic.Value)
so the producer never observes a nil Load. Also add a regression test that opens
a stream which closes immediately and registers a transport post-hook to verify
post-hooks/logs run and captured snapshots are released.
🧹 Nitpick comments (1)
framework/modelcatalog/pricing_test.go (1)

193-196: Consider using bifrost.Ptr() consistently.

The test uses new(0.000006) syntax here while other tests in this file (e.g., lines 107, 132-133) use bifrost.Ptr(). For consistency with the repository convention of preferring bifrost.Ptr() over raw pointer creation, consider standardizing on one approach. Based on learnings, bifrost.Ptr() is the preferred pattern in this codebase.

✨ Example for consistency
-	p.InputCostPerTokenAbove200kTokens = new(0.000006)
-	p.OutputCostPerTokenAbove200kTokens = new(0.00003)
-	p.InputCostPerTokenAbove272kTokens = new(0.000009)
-	p.OutputCostPerTokenAbove272kTokens = new(0.000045)
+	p.InputCostPerTokenAbove200kTokens = bifrost.Ptr(0.000006)
+	p.OutputCostPerTokenAbove200kTokens = bifrost.Ptr(0.00003)
+	p.InputCostPerTokenAbove272kTokens = bifrost.Ptr(0.000009)
+	p.OutputCostPerTokenAbove272kTokens = bifrost.Ptr(0.000045)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/modelcatalog/pricing_test.go` around lines 193 - 196, The test sets
pointer fields using new(...) for p.InputCostPerTokenAbove200kTokens,
p.OutputCostPerTokenAbove200kTokens, p.InputCostPerTokenAbove272kTokens, and
p.OutputCostPerTokenAbove272kTokens; replace those raw pointer usages with the
repository-preferred helper bifrost.Ptr(value) for consistency (e.g., call
bifrost.Ptr(0.000006) etc.) so the test matches other examples like the usages
at lines ~107 and 132-133 and to follow the codebase convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@transports/bifrost-http/handlers/middlewares.go`:
- Around line 462-476: runTransportPostHooksCaptured clones
headers/query/pathparams but doesn't copy the captured request payload, so
deferred post-hooks see an empty req.Body for streamed requests; update the
function (runTransportPostHooksCaptured) to deep-copy capturedReq.Body into the
pooled req.Body (preserving the same type/contents as capturedReq.Body) when
acquiring the new schemas.HTTPRequest so post-hook plugins read the original
payload (use a defensive copy or appropriate clone for the body representation
used in capturedReq.Body to avoid sharing/mutation).
- Around line 364-390: The deferred completer can be raced by the producer
goroutine; to fix, ensure the completer is published to the atomic slot before
the upstream goroutine can Load() it: create and Store a placeholder (or Store
the actual completer) into the ctx UserValue slot keyed by
BifrostContextKeyTransportPostHookCompleter immediately after constructing
completer (use slot.Store(completer) before returning from the middleware or
before calling next(ctx) where applicable), and keep the existing
capturedReq/capturedResp release in the completer; update
runTransportPostHooksCaptured usage and ensure slot.Store is used (atomic.Value)
so the producer never observes a nil Load. Also add a regression test that opens
a stream which closes immediately and registers a transport post-hook to verify
post-hooks/logs run and captured snapshots are released.

---

Nitpick comments:
In `@framework/modelcatalog/pricing_test.go`:
- Around line 193-196: The test sets pointer fields using new(...) for
p.InputCostPerTokenAbove200kTokens, p.OutputCostPerTokenAbove200kTokens,
p.InputCostPerTokenAbove272kTokens, and p.OutputCostPerTokenAbove272kTokens;
replace those raw pointer usages with the repository-preferred helper
bifrost.Ptr(value) for consistency (e.g., call bifrost.Ptr(0.000006) etc.) so
the test matches other examples like the usages at lines ~107 and 132-133 and to
follow the codebase convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e398f48a-6630-455a-b876-fccd0e65848c

📥 Commits

Reviewing files that changed from the base of the PR and between 36cf4ba and e614eb1.

⛔ Files ignored due to path filters (1)
  • plugins/prompts/go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/providers/azure/azure.go
  • core/providers/mistral/custom_provider_test.go
  • core/providers/mistral/errors.go
  • core/providers/mistral/mistral.go
  • core/providers/mistral/ocr_test.go
  • core/schemas/bifrost.go
  • core/schemas/chatcompletions.go
  • framework/modelcatalog/config.go
  • framework/modelcatalog/main.go
  • framework/modelcatalog/pricing.go
  • framework/modelcatalog/pricing_test.go
  • framework/modelcatalog/utils.go
  • framework/oauth2/main.go
  • plugins/prompts/go.mod
  • transports/bifrost-http/handlers/asyncinference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/pricing_integration_test.go
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/providers.go
✅ Files skipped from review due to trivial changes (7)
  • core/providers/mistral/ocr_test.go
  • plugins/prompts/go.mod
  • framework/oauth2/main.go
  • framework/modelcatalog/config.go
  • framework/modelcatalog/utils.go
  • core/schemas/bifrost.go
  • transports/bifrost-http/lib/config_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • transports/bifrost-http/handlers/config.go
  • core/bifrost_test.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/config.go
  • core/schemas/chatcompletions.go
  • transports/bifrost-http/lib/pricing_integration_test.go
  • core/providers/mistral/custom_provider_test.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 13, 2026
Copy link
Copy Markdown
Contributor Author

akshaydeo commented Apr 13, 2026

Merge activity

  • Apr 13, 7:57 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 13, 7:58 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo akshaydeo merged commit 4d486eb into v1.5.0 Apr 13, 2026
10 of 15 checks passed
@akshaydeo akshaydeo deleted the 04-13-exception_fixes branch April 13, 2026 07:58
@coderabbitai coderabbitai Bot mentioned this pull request Apr 13, 2026
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants