From 22a3aa91d68bc3bc4497a4dadbec20ad2f01abaa Mon Sep 17 00:00:00 2001 From: Dinesh Bhattarai Date: Thu, 24 Sep 2026 23:58:37 +0545 Subject: [PATCH] warn when a response-shaped malformed line is dropped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A malformed wire line that parses as a response is dropped without any log: it cannot be correlated to a request id, so a caller's pending request simply never resolves. Warn at the drop site (standalone line and batch entry) with the deserialization error and a bounded preview of the raw line, so lost replies — e.g. an agent whose plugins write escape sequences into the JSON-RPC stream — are diagnosable instead of silent hangs. --- .../src/jsonrpc/incoming_actor.rs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/agent-client-protocol/src/jsonrpc/incoming_actor.rs b/src/agent-client-protocol/src/jsonrpc/incoming_actor.rs index 35383168..f1f7051b 100644 --- a/src/agent-client-protocol/src/jsonrpc/incoming_actor.rs +++ b/src/agent-client-protocol/src/jsonrpc/incoming_actor.rs @@ -361,6 +361,17 @@ enum IncomingProtocolMsg { DynamicHandler(DynamicHandlerMessage), } +/// A bounded preview of a malformed wire line for logs: the line may be +/// arbitrarily large, and its text can carry user content. +fn log_preview(raw: &str) -> &str { + match raw.char_indices().nth(LOG_PREVIEW_CHARS) { + Some((idx, _)) => &raw[..idx], + None => raw, + } +} + +const LOG_PREVIEW_CHARS: usize = 200; + fn frame_entries( frame: TransportFrame, ) -> ( @@ -378,6 +389,15 @@ fn frame_entries( } TransportFrame::Malformed { raw, error } => { if raw_is_response_only_shape(&raw) { + // A malformed line that cannot be correlated to a request + // id is dropped — but dropping it silently hides exactly + // the replies a caller may still be waiting on. + tracing::warn!( + %error, + line = %log_preview(&raw), + "dropping a response-shaped line that failed to deserialize; \ + a pending request it may have answered will not resolve" + ); return (Vec::new(), None); } return ( @@ -390,7 +410,18 @@ fn frame_entries( .filter_map(|entry| match entry { TransportBatchEntry::Message(message) => Some(Ok(message)), TransportBatchEntry::Malformed { raw, error } => { - (!is_response_only_shape(&raw)).then_some(Err(error)) + if is_response_only_shape(&raw) { + tracing::warn!( + %error, + line = %log_preview(&raw.to_string()), + "dropping a response-shaped batch entry that failed to \ + deserialize; a pending request it may have answered \ + will not resolve" + ); + None + } else { + Some(Err(error)) + } } }) .collect(),