Severity: LOW
Component: crates/cli (daemon)
Summary
The daemon's per-subscription dedup treats an empty (or absent) message_id as "not yet seen" every time: BoundedMessageSubscriptionIds::insert("") returns true, and mark_stream_response_seen only records ids that pass that insert. So a stream response whose message_id is empty/missing is never recorded and is emitted on every recurrence — live delivery, events.recent_messages() replay, and the initial snapshot.
Location
crates/cli/src/daemon/protocol.rs:21-36 — BoundedMessageSubscriptionIds::insert returns true for an empty id (the empty-id guard).
crates/cli/src/daemon/responses.rs:374-400 — mark_stream_response_seen uses ...message_id...is_none_or(|id| seen.insert(id)), so None/empty short-circuits to "deliver."
Concrete failure scenario
A subscriber receives a stream response carrying an empty message_id (e.g. a synthesized or id-less update). It is never inserted into the bounded seen-set, so the same row is delivered again on the next live event, again on recent_messages() replay after a reconnect, and again in a fresh snapshot — the subscriber sees duplicates.
Impact
Duplicate delivery of empty-id rows to daemon subscribers. Low severity because real rows carry ids; this is the id-less/degenerate path.
Suggested fix
Guard the empty-id case in the daemon dedup path the same way the marmot-app recovery path does (#807): treat an empty/absent message_id as non-dedupable-but-log, or synthesize a stable dedup key, rather than always re-delivering.
Dedup
Sibling of #807 (marmot-app live-subscription dedupe doesn't guard empty message_id_hex) but a distinct layer — this is the CLI daemon fan-out (responses.rs/protocol.rs), not the marmot-app subscription path. Same class, different site (cf. the repo's practice of tracking distinct sibling sites separately, e.g. #856).
Filed from a meticulous automated code-review pass; verified against source and deduplicated against the existing open/closed issue set.
Severity: LOW
Component:
crates/cli(daemon)Summary
The daemon's per-subscription dedup treats an empty (or absent)
message_idas "not yet seen" every time:BoundedMessageSubscriptionIds::insert("")returnstrue, andmark_stream_response_seenonly records ids that pass that insert. So a stream response whosemessage_idis empty/missing is never recorded and is emitted on every recurrence — live delivery,events.recent_messages()replay, and the initial snapshot.Location
crates/cli/src/daemon/protocol.rs:21-36—BoundedMessageSubscriptionIds::insertreturnstruefor an empty id (the empty-id guard).crates/cli/src/daemon/responses.rs:374-400—mark_stream_response_seenuses...message_id...is_none_or(|id| seen.insert(id)), soNone/empty short-circuits to "deliver."Concrete failure scenario
A subscriber receives a stream response carrying an empty
message_id(e.g. a synthesized or id-less update). It is never inserted into the bounded seen-set, so the same row is delivered again on the next live event, again onrecent_messages()replay after a reconnect, and again in a fresh snapshot — the subscriber sees duplicates.Impact
Duplicate delivery of empty-id rows to daemon subscribers. Low severity because real rows carry ids; this is the id-less/degenerate path.
Suggested fix
Guard the empty-id case in the daemon dedup path the same way the marmot-app recovery path does (#807): treat an empty/absent
message_idas non-dedupable-but-log, or synthesize a stable dedup key, rather than always re-delivering.Dedup
Sibling of #807 (marmot-app live-subscription dedupe doesn't guard empty
message_id_hex) but a distinct layer — this is the CLI daemon fan-out (responses.rs/protocol.rs), not the marmot-app subscription path. Same class, different site (cf. the repo's practice of tracking distinct sibling sites separately, e.g. #856).Filed from a meticulous automated code-review pass; verified against source and deduplicated against the existing open/closed issue set.