Skip to content

fix: guard scoped context deadlines#3177

Open
ragokan wants to merge 4 commits intomaximhq:mainfrom
ragokan:okan/harden-bifrost-context-deadline
Open

fix: guard scoped context deadlines#3177
ragokan wants to merge 4 commits intomaximhq:mainfrom
ragokan:okan/harden-bifrost-context-deadline

Conversation

@ragokan
Copy link
Copy Markdown
Contributor

@ragokan ragokan commented May 1, 2026

Summary

Fix a BifrostContext panic that can occur when a child context is created from a scoped plugin context and that scoped context is later released back to the pool.

Fixes #3178.

Changes

  • NewBifrostContext now unwraps scoped BifrostContext parents to their root context, so child contexts do not retain pooled scoped contexts after ReleasePluginScope().
  • (*BifrostContext).Deadline() now safely handles nil receivers and cleared parents while preserving its own configured deadline.
  • Added regression tests for cleared parents, released scoped parents, and scoped-parent child contexts surviving scope release.
  • Added a core/changelog.md entry for the fix.

Design trade-off: this keeps the existing scoped-context pool behavior, but avoids storing scoped pooled contexts as child parents.

Type of change

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

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

From core/:

go test ./schemas -run 'TestBifrostContext_DeadlineWithClearedParent|TestBifrostContext_DeadlineWithClearedParentAndOwnDeadline|TestNewBifrostContext_ReleasedPluginScopeParent|TestNewBifrostContext_ScopedParentSurvivesScopeRelease|TestNewBifrostContext_NestedScopedParentSurvivesScopeRelease' -count=1
go test ./schemas -count=1
go test -race ./schemas -count=1

Expected outcome: all commands pass.

No new configs or environment variables were added.

Screenshots/Recordings

Not applicable; this is a Go core fix with no UI changes.

Breaking changes

  • Yes
  • No

Related issues

Fixes #3178.

Security considerations

No new auth, secret, PII, sandboxing, or network behavior is introduced. The change only hardens context parent/deadline handling.

Checklist

  • I read the available contributing guidelines (docs/contributing/raising-a-pr.mdx, docs/contributing/code-conventions.mdx); docs/contributing/README.md was not present on main
  • I added/updated tests where appropriate
  • I updated documentation where needed (core/changelog.md; no docs page needed)
  • I verified builds succeed (Go and UI) - not run; change is scoped to core/schemas
  • I verified the CI pipeline passes locally if applicable - not run; focused package tests and race test passed locally

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved deadline handling for scoped contexts so deadlines are preserved or reported absent correctly after scope release and when no deadline is set.
  • Tests
    • Added test coverage for deadline behavior and scope-release scenarios to ensure correct delegation and nil-safety.
  • Documentation
    • Added a changelog entry describing the scoped-context deadline guard.

Walkthrough

Normalizes delegated/scoped parents when creating new BifrostContext and hardens BifrostContext.Deadline to tolerate nil/cleared parent chains, avoiding nil pointer dereferences. Adds tests covering deadline behavior across plugin scope release and a changelog entry documenting the fix.

Changes

Cohort / File(s) Summary
Context implementation
core/schemas/context.go
NewBifrostContext walks valueDelegate chains to set the root (non-delegated) parent for new contexts. (*BifrostContext).Deadline tolerates a nil receiver and checks bc.parent == nil; it returns the context's own deadline only if hasDeadline is true, avoiding dereferencing cleared parents.
Tests
core/schemas/context_test.go
Adds five tests verifying BifrostContext.Deadline() with nil/cleared parents, explicit internal deadlines, delegation of trace ID from root, and behavior after ReleasePluginScope() calls including nested scoped parents.
Changelog
core/changelog.md
Adds a [fix] entry noting the guard for scoped context deadlines after plugin scope release.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I tunneled through scopes to find the root,
Untangled delegates and sealed each boot.
No more nil panics beneath the moonlight,
Deadlines stand steady, calm and right. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: guard scoped context deadlines' directly relates to the main change: hardening BifrostContext.Deadline() and NewBifrostContext to safely handle scoped plugin contexts.
Description check ✅ Passed The description comprehensively addresses all template sections: summary, detailed changes, type (bug fix), affected areas, test instructions, breaking changes status, related issues, and security considerations.
Linked Issues check ✅ Passed The PR fully addresses issue #3178 objectives: prevents panics in Deadline() when child contexts retain scoped parents, ensures safety after ReleasePluginScope(), provides comprehensive regression tests, and preserves existing pooling semantics.
Out of Scope Changes check ✅ Passed All changes directly address the linked issue #3178: context.go implements parent unwrapping and nil-safe Deadline() logic, context_test.go adds regression tests, and changelog.md documents the fix.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 1, 2026

Confidence Score: 5/5

Safe to merge — fix is correct and well-tested, no regressions identified.

The unwrap loop eliminates the hazard of holding a pooled scoped context as a child parent, the nil-parent guard in Deadline() closes the crash path, and tests cover nil receivers, cleared parents, released parents, and nested scoped parents. No P0 or P1 findings.

No files require special attention.

Important Files Changed

Filename Overview
core/schemas/context.go Adds recursive unwrap loop in NewBifrostContext to strip scoped parent references before storing, and adds nil-parent/nil-receiver guards to Deadline(); logic is sound and the primary panic path is correctly closed.
core/schemas/context_test.go Adds five regression tests covering nil receiver, cleared parent, released parent, single and nested scoped-parent survival; tests are correct and cover the primary fix scenarios well.
core/changelog.md Single-line changelog entry added for the fix.

Reviews (5): Last reviewed commit: "[test]: core schemas cover nil context d..." | Re-trigger Greptile

Comment thread core/schemas/context.go
Comment thread core/schemas/context.go Outdated
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

🧹 Nitpick comments (1)
core/schemas/context_test.go (1)

250-273: ⚡ Quick win

Add one nested-scope regression test (root -> scopedA -> scopedB -> child)

Current tests validate one scoped hop. Adding a nested-scope case would lock in behavior if WithPluginScope is ever called on a scoped context.

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

In `@core/schemas/context_test.go` around lines 250 - 273, Add a nested-scope
regression test mirroring TestNewBifrostContext_ScopedParentSurvivesScopeRelease
but with two scoped hops: create root := NewBifrostContext(...,
expectedDeadline) and set BifrostContextKeyTraceID, then scopedA :=
root.WithPluginScope(&nameA), scopedB := scopedA.WithPluginScope(&nameB), child
:= NewBifrostContext(scopedB, NoDeadline) (defer Cancel where appropriate), call
scopedB.ReleasePluginScope() and scopedA.ReleasePluginScope(), and assert
child.Deadline() still matches expectedDeadline and
child.Value(BifrostContextKeyTraceID) equals the root value; name the test e.g.
TestNewBifrostContext_NestedScopedParentSurvivesScopeRelease and reuse the same
helper symbols (NewBifrostContext, WithPluginScope, ReleasePluginScope,
BifrostContextKeyTraceID, NoDeadline).
🤖 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/context.go`:
- Around line 80-82: The assignment only unwraps one level of
BifrostContext.valueDelegate; update the logic in the context creation code so
it fully unwraps chained scoped parents by looping while parent is a
*BifrostContext with a non-nil valueDelegate (e.g., replace the single if check
with a for/for-like loop that repeatedly casts parent to *BifrostContext and
sets parent = parentCtx.valueDelegate until no further valueDelegate exists),
referencing the parent variable, the BifrostContext type, and its valueDelegate
field.

---

Nitpick comments:
In `@core/schemas/context_test.go`:
- Around line 250-273: Add a nested-scope regression test mirroring
TestNewBifrostContext_ScopedParentSurvivesScopeRelease but with two scoped hops:
create root := NewBifrostContext(..., expectedDeadline) and set
BifrostContextKeyTraceID, then scopedA := root.WithPluginScope(&nameA), scopedB
:= scopedA.WithPluginScope(&nameB), child := NewBifrostContext(scopedB,
NoDeadline) (defer Cancel where appropriate), call scopedB.ReleasePluginScope()
and scopedA.ReleasePluginScope(), and assert child.Deadline() still matches
expectedDeadline and child.Value(BifrostContextKeyTraceID) equals the root
value; name the test e.g.
TestNewBifrostContext_NestedScopedParentSurvivesScopeRelease and reuse the same
helper symbols (NewBifrostContext, WithPluginScope, ReleasePluginScope,
BifrostContextKeyTraceID, NoDeadline).
🪄 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: 09f92bde-e711-449a-a6c0-f72e934f5a87

📥 Commits

Reviewing files that changed from the base of the PR and between e351d72 and 69d2767.

📒 Files selected for processing (2)
  • core/schemas/context.go
  • core/schemas/context_test.go

Comment thread core/schemas/context.go Outdated
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)
core/schemas/context_test.go (1)

173-187: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a typed key for this context test.

SetValue/Value with raw strings violates the repository's no-raw-context-keys rule and can collide with other scoped values. Switch this test to a dedicated key type.

Suggested fix
 func TestNewBifrostContext_SetAndGetValue(t *testing.T) {
   ctx := NewBifrostContext(context.Background(), NoDeadline)

+  type testContextKey string
+  const key1 testContextKey = "key1"
+  const nonexistentKey testContextKey = "nonexistent"
+
   // Set a value
-  ctx.SetValue("key1", "value1")
+  ctx.SetValue(key1, "value1")

   // Get the value
-  if v := ctx.Value("key1"); v != "value1" {
+  if v := ctx.Value(key1); v != "value1" {
     t.Errorf("Expected value1, got %v", v)
   }

   // Get non-existent key
-  if v := ctx.Value("nonexistent"); v != nil {
+  if v := ctx.Value(nonexistentKey); v != nil {
     t.Errorf("Expected nil for non-existent key, got %v", v)
   }

As per coding guidelines, Rule: No raw context keys in Go.

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

In `@core/schemas/context_test.go` around lines 173 - 187, Replace raw string
context keys in TestNewBifrostContext_SetAndGetValue with a package-local typed
key to avoid raw-context-key collisions: declare a small key type (e.g., type
testCtxKey string) inside the test and use testCtxKey("key1") when calling
ctx.SetValue and ctx.Value (and testCtxKey("nonexistent") for the non-existent
lookup); keep using the same NewBifrostContext and NoDeadline setup and the same
assertions but reference the typed key instead of raw strings.
🤖 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/context_test.go`:
- Around line 211-216: Add a test that covers the nil-receiver path by calling
Deadline() on a nil *BifrostContext and asserting it returns ok==false (and does
not panic); either extend TestBifrostContext_DeadlineWithClearedParent or add a
new TestBifrostContext_DeadlineWithNilReceiver that does: var bc
*BifrostContext; deadline, ok := bc.Deadline(); if ok { t.Errorf(...) } to
ensure the nil pointer path for the Deadline() method is exercised.

---

Outside diff comments:
In `@core/schemas/context_test.go`:
- Around line 173-187: Replace raw string context keys in
TestNewBifrostContext_SetAndGetValue with a package-local typed key to avoid
raw-context-key collisions: declare a small key type (e.g., type testCtxKey
string) inside the test and use testCtxKey("key1") when calling ctx.SetValue and
ctx.Value (and testCtxKey("nonexistent") for the non-existent lookup); keep
using the same NewBifrostContext and NoDeadline setup and the same assertions
but reference the typed key instead of raw strings.
🪄 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: 3e200d73-c39a-40e3-ab90-2140f435dac6

📥 Commits

Reviewing files that changed from the base of the PR and between 6e0e0a3 and 3a12eae.

📒 Files selected for processing (2)
  • core/schemas/context.go
  • core/schemas/context_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/schemas/context.go

Comment thread core/schemas/context_test.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 1, 2026
@ragokan
Copy link
Copy Markdown
Contributor Author

ragokan commented May 1, 2026

Addressed the review feedback in the latest commits:

  • 3a12eae: fully unwraps chained scoped BifrostContext parents and adds a nested scoped-parent regression test.
  • 1304a1b: adds explicit nil-receiver coverage for Deadline().

Verified locally from core/:

go test ./schemas -run 'TestBifrostContext_DeadlineWithClearedParent|TestBifrostContext_DeadlineWithClearedParentAndOwnDeadline|TestNewBifrostContext_ReleasedPluginScopeParent|TestNewBifrostContext_ScopedParentSurvivesScopeRelease|TestNewBifrostContext_NestedScopedParentSurvivesScopeRelease' -count=1
go test ./schemas -count=1
go test -race ./schemas -count=1

@ragokan ragokan force-pushed the okan/harden-bifrost-context-deadline branch from 1304a1b to d4ae169 Compare May 1, 2026 16:28
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.

🧹 Nitpick comments (1)
core/schemas/context_test.go (1)

239-306: ⚡ Quick win

Prefer bifrost.Ptr() here, and drop the released-parent case.

This block still passes &name / &nameA / &nameB into WithPluginScope, and the repo convention is to use bifrost.Ptr(...) for those pointers. It also tests NewBifrostContext(released, ...), which leaves a pooled wrapper attached to the child instead of covering the actual child-before-release regression. The scoped-parent tests below already exercise the supported path. Based on learnings: prefer using bifrost.Ptr() to create pointers instead of the address operator (&) even when & would be valid syntactically.

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

In `@core/schemas/context_test.go` around lines 239 - 306, Replace uses of the
address operator when creating plugin scope names with the repo helper by
calling bifrost.Ptr("...") instead of &name/&nameA/&nameB in the tests that call
WithPluginScope (e.g., change scoped := root.WithPluginScope(&name) to scoped :=
root.WithPluginScope(bifrost.Ptr(name)) and similarly for scopedA/scopedB), and
remove the obsolete TestNewBifrostContext_ReleasedPluginScopeParent test
entirely so we only keep the supported scoped-parent and nested-scoped-parent
tests (TestNewBifrostContext_ScopedParentSurvivesScopeRelease and
TestNewBifrostContext_NestedScopedParentSurvivesScopeRelease) which exercise the
intended behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@core/schemas/context_test.go`:
- Around line 239-306: Replace uses of the address operator when creating plugin
scope names with the repo helper by calling bifrost.Ptr("...") instead of
&name/&nameA/&nameB in the tests that call WithPluginScope (e.g., change scoped
:= root.WithPluginScope(&name) to scoped :=
root.WithPluginScope(bifrost.Ptr(name)) and similarly for scopedA/scopedB), and
remove the obsolete TestNewBifrostContext_ReleasedPluginScopeParent test
entirely so we only keep the supported scoped-parent and nested-scoped-parent
tests (TestNewBifrostContext_ScopedParentSurvivesScopeRelease and
TestNewBifrostContext_NestedScopedParentSurvivesScopeRelease) which exercise the
intended behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae8f6b5c-817b-414a-b7fe-abf6f66aef78

📥 Commits

Reviewing files that changed from the base of the PR and between 1304a1b and d4ae169.

📒 Files selected for processing (3)
  • core/changelog.md
  • core/schemas/context.go
  • core/schemas/context_test.go
✅ Files skipped from review due to trivial changes (1)
  • core/changelog.md

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.

[Bug]: BifrostContext Deadline can panic after scoped plugin context release

1 participant