Skip to content

fix: preserve valid SSE events before parse errors#504

Merged
rapids-bot[bot] merged 2 commits into
release/0.6from
bbednarski/relay-513-nemo-relay060-rc2-valid-sse-frame-discarded-when-a-later
Jul 21, 2026
Merged

fix: preserve valid SSE events before parse errors#504
rapids-bot[bot] merged 2 commits into
release/0.6from
bbednarski/relay-513-nemo-relay060-rc2-valid-sse-frame-discarded-when-a-later

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Overview

Preserve valid SSE events parsed before a malformed frame in the same upstream read. This lets Switchyard observe the first successful stream item, commit the provider selection, and avoid fallback redispatch when the later frame fails to parse.

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

  • Add the non-breaking SseEventDecoder::push_bytes_results API to return successful events and the first later parse error in wire order.
  • Keep push_bytes compatible by collecting the ordered results into its existing return type.
  • Update the gateway SSE stream to yield valid JSON items before yielding the decoder error and terminating.
  • Preserve and normalize CRLF sequences split across adjacent upstream byte chunks.
  • Add codec and gateway regressions for a valid frame followed by malformed JSON in one byte batch, plus a CRLFCRLF terminator split across decoder calls.
  • Leave Switchyard commit logic unchanged; its existing first-success boundary remains authoritative.

Validation:

  • Focused codec regression group: passed (8 tests).
  • Focused gateway regression with Switchyard enabled: passed.
  • Switchyard streaming_never_retries_after_first_item: passed.
  • cargo fmt --all: passed.
  • Workspace Clippy with warnings denied: passed.
  • Full Rust suite: passed.
  • Python suite: passed (535 tests).
  • Go suite: passed.
  • Node suite: passed (277 tests).
  • Targeted pre-commit: passed.
  • All non-attribution pre-commit hooks: passed. Attribution generation remains excluded because the Python generator stalls and the Rust generator produces an unrelated generated-file rewrite in this checkout.
  • The external SQA RELAY-513/RELAY-003 tests were not available in this checkout.

No breaking changes.

Where should the reviewer start?

Start with SseEventDecoder::push_bytes_results in crates/core/src/codec/streaming.rs, then follow its ordered results into sse_json_stream in crates/cli/src/gateway/mod.rs.

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

  • Fixes RELAY-513 (NVBug 6480525)

Summary by CodeRabbit

  • Bug Fixes
    • SSE streams now emit successfully decoded events that arrive before a later malformed event.
    • Improved decoding of SSE frame boundaries across chunked input (including more robust CRLF handling).
    • Error messages now include decoding context and the original invalid JSON payload, and streams terminate right after the first failure.
  • Tests
    • Added unit coverage for per-frame decode result behavior and CRLF split handling.
    • Added an async coverage test ensuring mixed valid/invalid SSE messages in one response batch behave correctly.

Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@github-actions github-actions Bot added size:S PR is small Bug issue describes bug; PR fixes bug lang:rust PR changes/introduces Rust code labels Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

SSE decoding now returns per-frame results, allowing valid events to be yielded before a later JSON parsing error. The gateway propagates that error and terminates, with unit and integration tests covering the behavior.

Changes

SSE partial decode propagation

Layer / File(s) Summary
Per-frame decoder results
crates/core/src/codec/streaming.rs, crates/core/tests/unit/codec/streaming_tests.rs
push_bytes_results returns ordered frame results through the first parsing error, handles split CRLF terminators, and preserves the existing aggregate API. Unit coverage verifies both behaviors.
Gateway stream propagation
crates/cli/src/gateway/mod.rs, crates/cli/tests/coverage/shared/gateway_tests.rs
sse_json_stream yields successful event data, then yields the first decoding error and stops. Integration coverage exercises a valid frame followed by invalid JSON.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TcpServer
  participant sse_json_stream
  participant SseEventDecoder
  TcpServer->>sse_json_stream: SSE response with valid and invalid frames
  sse_json_stream->>SseEventDecoder: Decode buffered frames
  SseEventDecoder-->>sse_json_stream: Valid event
  sse_json_stream-->>TcpServer: Yield event data
  SseEventDecoder-->>sse_json_stream: JSON decoding error
  sse_json_stream-->>TcpServer: Yield error and terminate
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows Conventional Commits and clearly summarizes the main SSE parse-error handling change.
Description check ✅ Passed The description includes the required overview, details, reviewer start point, and related issue section with validation notes.
✨ 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 bbednarski/relay-513-nemo-relay060-rc2-valid-sse-frame-discarded-when-a-later

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

@github-actions

Copy link
Copy Markdown

@bbednarski9
bbednarski9 marked this pull request as ready for review July 20, 2026 22:02
@bbednarski9
bbednarski9 requested a review from a team as a code owner July 20, 2026 22:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@crates/core/src/codec/streaming.rs`:
- Around line 139-144: Update push_bytes_results to normalize CRLF sequences
that span successive bytes chunks, preserving a trailing carriage return until
the next append can determine whether it forms CRLF; ensure frame detection
still recognizes a terminator split across calls. Add a regression test that
splits the final CRLF of a CRLFCRLF terminator between two push_bytes_results
calls.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 181e35f1-a989-430f-b80c-379a1f537a33

📥 Commits

Reviewing files that changed from the base of the PR and between 7766342 and ee5f3ff.

📒 Files selected for processing (4)
  • crates/cli/src/gateway/mod.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/core/src/codec/streaming.rs
  • crates/core/tests/unit/codec/streaming_tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Node.js / Test (windows-arm64)
  • GitHub Check: Python / Test (windows-arm64)
  • GitHub Check: Rust / Test (windows-arm64)
  • GitHub Check: Rust / Test (windows-amd64)
🧰 Additional context used
📓 Path-based instructions (15)
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
{crates/core,crates/adaptive}/**/*

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

Changes to crates/core or crates/adaptive must run the full language matrix

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/core/src/codec/streaming.rs
crates/core/**/*.rs

📄 CodeRabbit inference engine (.agents/skills/test-go-binding/SKILL.md)

If the change touched crates/core or shared runtime semantics, also use validate-change for broader validation

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/core/src/codec/streaming.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
crates/{core,adaptive}/**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If crates/core or crates/adaptive changed, run the full validation matrix across Rust, Python, Go, and Node.js.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/core/src/codec/streaming.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
**/*.{rs,py,js,ts,tsx,go,java,kt,swift}

📄 CodeRabbit inference engine (.agents/skills/add-middleware/SKILL.md)

Add tests covering registration and duplicate names, deregistration and missing names, priority ordering, callback failure policy, scope-local inheritance and cleanup, event payload semantics, immutable mark and scope fields, and parity across affected bindings.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
crates/{core,adaptive}/**/*.rs

⚙️ CodeRabbit configuration file

crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/core/src/codec/streaming.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/core/tests/unit/codec/streaming_tests.rs
  • crates/cli/tests/coverage/shared/gateway_tests.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/cli/src/gateway/mod.rs
  • crates/core/src/codec/streaming.rs
crates/core/src/{api/**/*.rs,api/runtime/**/*.rs,codec/**/*.rs,json.rs}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Implement the new or changed public runtime behavior first in the Rust core, especially under crates/core/src/api/ and related core modules such as crates/core/src/api/runtime/, crates/core/src/codec/, and crates/core/src/json.rs.

Files:

  • crates/core/src/codec/streaming.rs
🔇 Additional comments (5)
crates/core/src/codec/streaming.rs (2)

130-132: LGTM!


131-159: 📐 Maintainability & Code Quality

Confirm the required core-runtime validation ran.

Please include successful validation evidence for cargo fmt --all, cargo clippy --workspace --all-targets -- -D warnings, just test-rust, cargo deny check, validate-change, the full Rust/Python/Go/Node matrix, and final pre-commit.

As per coding guidelines, “If the change touched crates/core or shared runtime semantics, also use validate-change for broader validation” and changes to crates/core “must run the full validation matrix across Rust, Python, Go, and Node.js.”

Source: Coding guidelines

crates/core/tests/unit/codec/streaming_tests.rs (1)

80-97: LGTM!

crates/cli/src/gateway/mod.rs (1)

522-529: LGTM!

crates/cli/tests/coverage/shared/gateway_tests.rs (1)

877-914: LGTM!

Comment thread crates/core/src/codec/streaming.rs
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.34%. Comparing base (edd3aee) to head (ef5913b).
⚠️ Report is 12 commits behind head on release/0.6.

Additional details and impacted files
@@               Coverage Diff               @@
##           release/0.6     #504      +/-   ##
===============================================
+ Coverage        93.31%   93.34%   +0.03%     
===============================================
  Files              313      313              
  Lines            81604    81623      +19     
  Branches           100      100              
===============================================
+ Hits             76142    76183      +41     
+ Misses            5462     5440      -22     
Components Coverage Δ
Rust Runtime 94.80% <100.00%> (+0.04%) ⬆️
Shared DTO Types 98.76% <ø> (ø)
CLI 91.11% <88.89%> (-0.01%) ⬇️
Go Binding 95.03% <100.00%> (+0.05%) ⬆️
Python Binding 94.95% <40.00%> (ø)
Node Binding 98.33% <ø> (ø)
Dynamic Plugin SDKs 97.52% <ø> (ø)
Files with missing lines Coverage Δ
crates/cli/src/gateway/mod.rs 91.48% <ø> (+0.07%) ⬆️
crates/core/src/codec/streaming.rs 100.00% <100.00%> (ø)

... and 8 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7766342...ef5913b. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@willkill07 willkill07 added this to the 0.6 milestone Jul 20, 2026
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@github-actions github-actions Bot added size:M PR is medium and removed size:S PR is small labels Jul 21, 2026
@bbednarski9

Copy link
Copy Markdown
Contributor Author

/ok to test ef5913b

@bbednarski9

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 0bf2064 into release/0.6 Jul 21, 2026
69 of 71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug issue describes bug; PR fixes bug lang:rust PR changes/introduces Rust code size:M PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants