Skip to content

fix(device): stop broadcasting the malformed first stream frame (closes #425) - #428

Merged
tylerkron merged 4 commits into
mainfrom
fix/425-first-frame-guard
Aug 2, 2026
Merged

fix(device): stop broadcasting the malformed first stream frame (closes #425)#428
tylerkron merged 4 commits into
mainfrom
fix/425-first-frame-guard

Conversation

@tylerkron

Copy link
Copy Markdown
Contributor

Why

On the firmware that ships today (3.7.2), the very first frame of every stream carries one analog value no matter how many channels you enabled. Core already hid that from its decoded per-channel path, but handed the bad frame straight to raw-frame consumers — which is the path most callers actually use. The example CLI's offline export read a channel count of one off it and silently truncated every sample after it (example-app#34). Exit codes stayed 0 the whole time.

What

That first frame no longer reaches consumers on either path. A well-formed first frame is completely unaffected, so this is safe to leave in permanently once the firmware fix ships. Because silently dropping data is its own kind of bug, every drop is now reported: a new StreamFrameDiscarded event says which frame went and why, and DiscardedStreamFrameCount gives a running total for the session.

How

The partial-frame check that already existed for the decoded path moved up one level, so it now gates the raw MessageReceived event too — a frame with fewer analog values than enabled channels is withheld from both. Its digital payload is still decoded and its timestamp still anchors the session clock, exactly as before.

This also adds the cross-session leftover guard #351 asked for (firmware #533), as a small StreamFrameGate collaborator rather than more state on the device class. The device latches the last frame of a stopped session and replays it at the next start; the guard spots it by its device-tick counter, using modular uint arithmetic so it stays correct across the counter's 86-second wrap. Two deliberate departures from the desktop reference it was ported from: the window is measured in sample periods rather than a fixed 2.5 s, and comparisons are made against a counter fixed at session start rather than the last frame seen — together those stop a quick stop/start from cascading into a run of discarded real frames, which the reference implementation would do. Discards are capped as a backstop.

Behaviour change worth knowing about: MessageReceived / StreamMessageReceived no longer fire for a frame Core rejects. That is the fix, but a consumer that counts raw frames will see one fewer at stream start — StreamFrameDiscarded is how you tell that apart from a dropout.

Bench test

Real Nq1, FW 3.7.2, example CLI built against this branch. The raw serial bytes confirm the defect is on the wire, not in decoding — frame 1 is 12 01 02 (analog field, 1 byte), frame 2 is 12 04 04 10 00 00 (4 bytes).

USB/serial — the issue's exact repro, --rate 20 --limit 4 --channels 15:

BEFORE (main @359bf74)                        AFTER (this branch)
{"ts":1576597117,"analog":[1]}        <-- 1   {"ts":1821079183,"analog":[2,8,0,0]}
{"ts":1578697117,"analog":[2,8,0,0]}          {"ts":1823179183,"analog":[3,8,0,0]}
{"ts":1580797118,"analog":[3,7,0,0]}          {"ts":1825279183,"analog":[3,7,0,0]}
{"ts":1582897117,"analog":[3,8,0,0]}          {"ts":1827379183,"analog":[2,8,0,0]}

Steady state, USB, 25 s — no drops or stall introduced:

samples: 395   analog width histogram: {4: 395}
delta ticks min/median/max: 2099992 / 2100000 / 2100007
deltas > 2x median (dropouts): 0

WiFi/TCP 192.168.1.30, --channels 3 (one consolidated connect):

{"ts":241286096,"analog":[2,8],"digital":""}   <-- 2 values, was [2] before
{"ts":243386096,"analog":[2,7],"digital":""}
samples: 189   analog width histogram: {2: 189}

Full Core suite green on net9.0 and net10.0 (2487 passed).

Closes #425

#425)

Firmware up to and including 3.7.2 emits a leading frame at stream start
whose analog payload holds a single value regardless of the enabled channel
mask. Core's per-channel decode has guarded against it since #351, but the
raw MessageReceived event was still handed the frame verbatim — and that is
the path most callers use, including the example CLI, whose offline export
inferred a channel count of one from it and truncated every sample that
followed (daqifi-core-example-app#34).

Both consumer paths are now gated together, and every drop is reported
through the new StreamFrameDiscarded event and DiscardedStreamFrameCount so
a suppressed frame is never invisible.

Also adds the cross-session leftover-frame guard #351 asked for
(daqifi-nyquist-firmware #533) as a StreamFrameGate collaborator: the frame
the device latches across a stop is recognised by its device-tick counter
using wrap-safe modular arithmetic, measured against a reference fixed at
session start so a quick restart cannot cascade, and capped so a stream can
never be withheld indefinitely.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron
tylerkron requested a review from a team as a code owner August 2, 2026 16:55
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix: discard malformed first stream frame and report discards

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Withhold malformed leading stream frames from both raw and decoded consumer paths.
• Emit StreamFrameDiscarded and track DiscardedStreamFrameCount for observable, counted drops.
• Add a wrap-safe StreamFrameGate to reject cross-session leftover frames at session start.
Diagram

graph TD
  A["DaqifiStreamingDevice\nOnStreamMessageReceived"] --> B["StreamFrameGate"] --> C{"Leftover\nframe?"}
  C -->|"yes"| D(("StreamFrameDiscarded\n+ count"))
  C -->|"no"| E["EmitStreamFrame"] --> F{"Partial\nanalog?"}
  F -->|"yes"| D
  F -->|"no"| G["MessageReceived\n(raw)"] --> H["DecodeStreamFrame\n(per-channel)"]

  subgraph Legend
    direction LR
    _step["Process step"] ~~~ _dec{"Decision"} ~~~ _evt(("Event"))
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Tag-and-forward malformed frames (instead of dropping)
  • ➕ Raw consumers still receive every on-wire frame for auditing/replay.
  • ➕ Avoids behavioral change for consumers that count raw frames.
  • ➖ Still breaks consumers that infer channel width from the first frame unless every consumer is updated.
  • ➖ Requires new message metadata plumbed through every raw consumer path.
2. Hold first frame until a second frame validates it
  • ➕ Can protect the very first session after connect from stale leftovers too.
  • ➕ Avoids relying on a pre-session reference counter value.
  • ➖ Delays first usable sample for every session (latency/regression).
  • ➖ More buffering/state complexity; harder to reason about clock anchoring and edge timing.
3. Rely solely on firmware fixes
  • ➕ No host-side behavioral changes or extra events/APIs.
  • ➕ Keeps core logic simpler long-term if firmware rollout is guaranteed.
  • ➖ Does not protect existing deployed firmware (e.g., 3.7.2) and callers today.
  • ➖ Leaves raw-frame consumers exposed to silent truncation/incorrect exports.

Recommendation: Keep the PR’s approach: dropping frames that are provably unsafe for consumers is the most robust fix for deployed firmware, and emitting StreamFrameDiscarded + DiscardedStreamFrameCount addresses observability concerns from sample-counting clients. The StreamFrameGate collaborator cleanly isolates the cross-session heuristic while staying wrap-safe and bounded.

Files changed (6) +860 / -41

Enhancement (3) +264 / -0
StreamFrameGate.csIntroduce StreamFrameGate for cross-session leftover-frame suppression +168/-0

Introduce StreamFrameGate for cross-session leftover-frame suppression

• Adds a small internal collaborator that detects and discards stale first frames from a previous session within a window measured in sample periods. Uses wrap-safe uint subtraction, disables itself after a genuine frame, and caps discards per session as a backstop.

src/Daqifi.Core/Device/Internal/StreamFrameGate.cs

StreamFrameDiscardReason.csDefine discard reasons for withheld stream frames +39/-0

Define discard reasons for withheld stream frames

• Adds an enum describing why a stream frame was withheld (partial analog warmup vs stale leftover from previous session), with documentation tying each to known firmware-side defects.

src/Daqifi.Core/Device/StreamFrameDiscardReason.cs

StreamFrameDiscardedEventArgs.csAdd event args payload for StreamFrameDiscarded +57/-0

Add event args payload for StreamFrameDiscarded

• Introduces an EventArgs type carrying discard reason, device timestamp, analog value count, and enabled analog channel count so consumers can distinguish drops from device silence.

src/Daqifi.Core/Device/StreamFrameDiscardedEventArgs.cs

Bug fix (1) +172 / -33
DaqifiStreamingDevice.csGate stream frames before raw/decoded delivery; add discard event and counter +172/-33

Gate stream frames before raw/decoded delivery; add discard event and counter

• Moves the partial-analog warmup suppression to gate both raw and decoded paths, ensuring malformed first frames never reach consumers. Adds 'StreamFrameDiscarded' and 'DiscardedStreamFrameCount', plus a 'StreamFrameGate' to reject stale cross-session leftover frames using modular timestamp arithmetic.

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs

Tests (2) +424 / -8
DaqifiStreamingDeviceDecodeTests.csExpand decode tests for frame discards and cross-session leftovers +247/-8

Expand decode tests for frame discards and cross-session leftovers

• Updates and adds unit tests to assert malformed warmup frames are not delivered to raw or decoded consumers. Adds coverage for discard reporting/counting and for the new cross-session leftover-frame behavior, including restart timing scenarios.

src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs

StreamFrameGateTests.csAdd unit tests for StreamFrameGate wrap-safe leftover detection +177/-0

Add unit tests for StreamFrameGate wrap-safe leftover detection

• Introduces focused tests for the leftover-frame gate: reference seeding, validation lifecycle, window scaling by sample rate, counter wrap correctness, quick restart bounded discards, and discard capping.

src/Daqifi.Core.Tests/Device/Internal/StreamFrameGateTests.cs

@qodo-code-review

qodo-code-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Trace logging breaks isolation ✓ Resolved 🐞 Bug ☼ Reliability
Description
RaiseStreamFrameDiscarded catches subscriber exceptions but then calls Trace.WriteLine inside the
catch; a throwing TraceListener can let the exception escape and disrupt stream-frame processing.
This undermines the method’s stated guarantee that misbehaving handlers cannot affect subsequent
frames.
Code

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[R1001-1004]

+            catch (Exception ex)
+            {
+                Trace.WriteLine($"[{nameof(StreamFrameDiscarded)}] Subscriber threw: {ex}");
+            }
Relevance

●●● Strong

Exact accepted precedent: logging in isolation catch must be best-effort since Trace listeners can
throw.

PR-#354

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new discard event explicitly isolates subscriber exceptions, but the isolation can be defeated
because the catch block performs an unguarded Trace.WriteLine. The base device code demonstrates
the established pattern in this repo: logging must be wrapped because sinks can throw; a past
accepted bug also documents this exact risk (Trace listeners throwing).

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[979-1004]
src/Daqifi.Core/Device/DaqifiDevice.cs[4055-4088]
PR-#354

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`RaiseStreamFrameDiscarded(...)` is intended to isolate subscriber exceptions, but its `catch` block uses `Trace.WriteLine(...)` directly. If a custom `TraceListener` throws during `WriteLine`, the exception can escape the isolation boundary and potentially break the stream-message pipeline.

### Issue Context
The base device layer already treats logging sinks as untrusted and wraps logging calls (`SafeLog(...)`) so logging cannot throw back into device operation.

### Fix Focus Areas
- Ensure logging in `RaiseStreamFrameDiscarded` cannot throw (wrap `Trace.WriteLine` in its own best-effort try/catch, or route through an exception-safe logger helper).
- Consider extracting a small helper (e.g., `SafeTraceWriteLine(...)`) and reusing it for similar patterns in the file.

#### Fix Focus Areas (code pointers)
- src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[979-1004]
- src/Daqifi.Core/Device/DaqifiDevice.cs[4055-4088]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Global Trace listener flakiness ✓ Resolved 🐞 Bug ☼ Reliability ⭐ New
Description
The new Decode_ThrowingDiscardSubscriberAndThrowingTraceListener_DoesNotBreakTheStream test installs
a throwing TraceListener into the process-global Trace.Listeners collection, so any concurrent
Trace.WriteLine containing the marker ("StreamFrameDiscarded") can be affected while the listener is
installed. This creates cross-test contamination risk and can cause intermittent failures if any
other test/framework code writes that marker without a SafeTrace-style wrapper.
Code

src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs[R685-687]

+        var listener = new ThrowOnMarkerTraceListener("StreamFrameDiscarded");
+        Trace.Listeners.Add(listener);
+        try
Relevance

●●● Strong

Team has accepted hardening against throwing TraceListeners and test flakiness; likely to isolate
global Trace.Listeners usage.

PR-#354
PR-#350

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test installs a custom listener into the global Trace listener list and that listener throws
when the marker is seen; meanwhile, the production code logs discard-subscriber failures via
SafeTrace using nameof(StreamFrameDiscarded), which matches the same marker string.

src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs[671-706]
src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs[1077-1097]
src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[1034-1056]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A unit test adds a throwing `TraceListener` to the global `Trace.Listeners` collection. Because this listener is process-wide, any other code running concurrently in the same test process that writes a trace line containing the marker can be impacted during the window the listener is installed.

### Issue Context
This test is meant to validate that `SafeTrace(...)` prevents a throwing `TraceListener` from breaking the stream pipeline, but the test’s global listener can still interfere with other tests that might emit the same marker.

### Fix Focus Areas
- src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs[671-706]
- src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs[1077-1099]

### Suggested fix
Constrain `ThrowOnMarkerTraceListener` so it only throws for trace writes originating from this test’s execution context (e.g., capture `Environment.CurrentManagedThreadId` at construction time and return early if the current thread differs). This keeps the test’s assertion intact while preventing cross-test interference in parallel runs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Discard counts can mismatch ✓ Resolved 🐞 Bug ◔ Observability
Description
For PartialAnalogFrame discards, the enabled-analog-channel count is computed once to decide
suppression and then recomputed again when constructing StreamFrameDiscardedEventArgs, so
EnabledAnalogChannelCount can disagree with the suppression decision if channel enablement changes
concurrently. This makes the new discard telemetry potentially self-inconsistent for consumers
trying to reconcile drops.
Code

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[R995-999]

+                handler(this, new StreamFrameDiscardedEventArgs(
+                    reason,
+                    frame.MsgTimeStamp,
+                    analogValueCount,
+                    CountEnabledAnalogChannels(SnapshotChannels())));
Relevance

●●● Strong

Team has prior accepted fixes around channel-snapshot races; making discard telemetry consistent is
low-risk.

PR-#362
PR-#411

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The suppression decision snapshots channels to compute enabledAnalogCount, but the event args
later compute EnabledAnalogChannelCount via a second SnapshotChannels() call. Since
SnapshotChannels() returns a point-in-time copy, two calls can observe different states if a
concurrent enable/disable occurs, yielding inconsistent telemetry.

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[947-999]
src/Daqifi.Core/Device/DaqifiDevice.cs[251-272]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
For `StreamFrameDiscardReason.PartialAnalogFrame`, the enabled analog channel count used to decide suppression is recomputed again when raising `StreamFrameDiscarded`. If channel enablement changes between these two snapshots, the event can report an enabled-channel count that doesn’t match the suppression decision.

### Issue Context
This is specific to the warmup/partial-analog suppression flow (`ShouldSuppressPartialAnalog(...)` -> `RaiseStreamFrameDiscarded(...)`). Each `SnapshotChannels()` call is consistent by itself, but the pair is not atomic.

### Fix Focus Areas
- Capture `enabledAnalogCount` once for the suppression decision and reuse that same value when raising the discard event for `PartialAnalogFrame`.
- (Optional) Similarly reuse the computed `analogValueCount` to avoid recomputation.

#### Fix Focus Areas (code pointers)
- src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[947-1004]
- src/Daqifi.Core/Device/DaqifiDevice.cs[251-272]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Previous review results

Review updated until commit c19412a

Results up to commit ad0cb0e ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Trace logging breaks isolation ✓ Resolved 🐞 Bug ☼ Reliability
Description
RaiseStreamFrameDiscarded catches subscriber exceptions but then calls Trace.WriteLine inside the
catch; a throwing TraceListener can let the exception escape and disrupt stream-frame processing.
This undermines the method’s stated guarantee that misbehaving handlers cannot affect subsequent
frames.
Code

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[R1001-1004]

+            catch (Exception ex)
+            {
+                Trace.WriteLine($"[{nameof(StreamFrameDiscarded)}] Subscriber threw: {ex}");
+            }
Relevance

●●● Strong

Exact accepted precedent: logging in isolation catch must be best-effort since Trace listeners can
throw.

PR-#354

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new discard event explicitly isolates subscriber exceptions, but the isolation can be defeated
because the catch block performs an unguarded Trace.WriteLine. The base device code demonstrates
the established pattern in this repo: logging must be wrapped because sinks can throw; a past
accepted bug also documents this exact risk (Trace listeners throwing).

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[979-1004]
src/Daqifi.Core/Device/DaqifiDevice.cs[4055-4088]
PR-#354

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`RaiseStreamFrameDiscarded(...)` is intended to isolate subscriber exceptions, but its `catch` block uses `Trace.WriteLine(...)` directly. If a custom `TraceListener` throws during `WriteLine`, the exception can escape the isolation boundary and potentially break the stream-message pipeline.

### Issue Context
The base device layer already treats logging sinks as untrusted and wraps logging calls (`SafeLog(...)`) so logging cannot throw back into device operation.

### Fix Focus Areas
- Ensure logging in `RaiseStreamFrameDiscarded` cannot throw (wrap `Trace.WriteLine` in its own best-effort try/catch, or route through an exception-safe logger helper).
- Consider extracting a small helper (e.g., `SafeTraceWriteLine(...)`) and reusing it for similar patterns in the file.

#### Fix Focus Areas (code pointers)
- src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[979-1004]
- src/Daqifi.Core/Device/DaqifiDevice.cs[4055-4088]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Discard counts can mismatch ✓ Resolved 🐞 Bug ◔ Observability
Description
For PartialAnalogFrame discards, the enabled-analog-channel count is computed once to decide
suppression and then recomputed again when constructing StreamFrameDiscardedEventArgs, so
EnabledAnalogChannelCount can disagree with the suppression decision if channel enablement changes
concurrently. This makes the new discard telemetry potentially self-inconsistent for consumers
trying to reconcile drops.
Code

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[R995-999]

+                handler(this, new StreamFrameDiscardedEventArgs(
+                    reason,
+                    frame.MsgTimeStamp,
+                    analogValueCount,
+                    CountEnabledAnalogChannels(SnapshotChannels())));
Relevance

●●● Strong

Team has prior accepted fixes around channel-snapshot races; making discard telemetry consistent is
low-risk.

PR-#362
PR-#411

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The suppression decision snapshots channels to compute enabledAnalogCount, but the event args
later compute EnabledAnalogChannelCount via a second SnapshotChannels() call. Since
SnapshotChannels() returns a point-in-time copy, two calls can observe different states if a
concurrent enable/disable occurs, yielding inconsistent telemetry.

src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[947-999]
src/Daqifi.Core/Device/DaqifiDevice.cs[251-272]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
For `StreamFrameDiscardReason.PartialAnalogFrame`, the enabled analog channel count used to decide suppression is recomputed again when raising `StreamFrameDiscarded`. If channel enablement changes between these two snapshots, the event can report an enabled-channel count that doesn’t match the suppression decision.

### Issue Context
This is specific to the warmup/partial-analog suppression flow (`ShouldSuppressPartialAnalog(...)` -> `RaiseStreamFrameDiscarded(...)`). Each `SnapshotChannels()` call is consistent by itself, but the pair is not atomic.

### Fix Focus Areas
- Capture `enabledAnalogCount` once for the suppression decision and reuse that same value when raising the discard event for `PartialAnalogFrame`.
- (Optional) Similarly reuse the computed `analogValueCount` to avoid recomputation.

#### Fix Focus Areas (code pointers)
- src/Daqifi.Core/Device/DaqifiStreamingDevice.cs[947-1004]
- src/Daqifi.Core/Device/DaqifiDevice.cs[251-272]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment thread src/Daqifi.Core/Device/DaqifiStreamingDevice.cs
Comment thread src/Daqifi.Core/Device/DaqifiStreamingDevice.cs Outdated
…iscard counts from the decision

Addresses both Qodo findings on #428.

Trace dispatches to consumer-installed listeners, so logging from inside the
catch that contains a bad subscriber is itself consumer code — a throwing
listener escapes the containment and takes down the frame pipeline the catch
was protecting. Routed through a new SafeTrace helper, mirroring the
guarantee DaqifiDevice.SafeLog already gives RaiseClassifiedEvent. Applied to
the two other Trace sites in the file as well: RaiseGapDetected had the same
unguarded pattern I copied, and TrackStreamingStart documents that tracking a
command must never fail the send that carried it.

StreamFrameDiscarded now reports the analog and enabled-channel counts the
suppression decision was actually made on, instead of re-reading channel
state that another thread may have changed in between. Self-inconsistent
telemetry would undermine the observability this PR exists to add.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

Comment thread src/Daqifi.Core.Tests/Device/DaqifiStreamingDeviceDecodeTests.cs Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e256014

…bal Trace state

Trace.Listeners is process-global, so a test that installs a throwing
listener can be reached by anything else running in the same process. This
was the only Trace.Listeners usage in the repo, and it was added to a suite
that is concurrently being destabilised by exactly that class of problem
(#430).

The test now proves the property that actually ships — a throwing
StreamFrameDiscarded subscriber does not break the frame pipeline, and the
next frame still decodes — using only a throwing subscriber, with a call
counter so it cannot pass vacuously. SafeTrace stays in production, where it
is the real fix; the codebase already treats its twin, DaqifiDevice.SafeLog,
as covered through an injected logger rather than global state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 4165dff

@tylerkron
tylerkron merged commit 89759ed into main Aug 2, 2026
1 check passed
@tylerkron
tylerkron deleted the fix/425-first-frame-guard branch August 2, 2026 21:55
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.

Core still broadcasts the malformed first stream frame — host-side protection recommended in #351 was never implemented

1 participant