Skip to content

Commit d508b0d

Browse files
committed
fix(session): skip non-JSON lines when reading Gemini CLI output
- Add loop to read lines until a valid JSON line is encountered - Skip empty or non‑JSON lines (e.g., warnings, debug info) - Continue emitting CLI output events for each line read - Introduce tests to ensure non‑JSON lines are ignored and JSON lines are parsed correctly
1 parent 22a1cea commit d508b0d

File tree

1 file changed

+69
-16
lines changed
  • crates/backend/src/session

1 file changed

+69
-16
lines changed

crates/backend/src/session/mod.rs

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -217,26 +217,43 @@ async fn send_jsonrpc_request<E: EventEmitter>(
217217
},
218218
);
219219

220-
// Read response
220+
// Read response - keep reading lines until we get valid JSON
221221
let mut line = String::new();
222-
reader
223-
.read_line(&mut line)
224-
.await
225-
.map_err(|e| BackendError::SessionInitFailed(format!("Failed to read response: {e}")))?;
222+
let trimmed_line = loop {
223+
line.clear();
224+
reader
225+
.read_line(&mut line)
226+
.await
227+
.map_err(|e| BackendError::SessionInitFailed(format!("Failed to read response: {e}")))?;
226228

227-
let trimmed_line = line.trim();
228-
println!("🔍 RAW OUTPUT FROM GEMINI CLI: {trimmed_line}");
229-
let _ = rpc_logger.log_rpc(line.trim());
229+
let trimmed = line.trim();
230+
println!("🔍 RAW OUTPUT FROM GEMINI CLI: {trimmed}");
231+
let _ = rpc_logger.log_rpc(trimmed);
230232

231-
let _ = emitter.emit(
232-
&format!("cli-io-{session_id}"),
233-
CliIoPayload {
234-
io_type: CliIoType::Output,
235-
data: line.trim().to_string(),
236-
},
237-
);
233+
let _ = emitter.emit(
234+
&format!("cli-io-{session_id}"),
235+
CliIoPayload {
236+
io_type: CliIoType::Output,
237+
data: trimmed.to_string(),
238+
},
239+
);
240+
241+
// Skip non-JSON lines like "Data collection is disabled."
242+
if trimmed.is_empty() || (!trimmed.starts_with('{') && !trimmed.starts_with('[')) {
243+
println!("🔍 Skipping non-JSON line: {trimmed}");
244+
continue;
245+
}
238246

239-
let response = serde_json::from_str::<JsonRpcResponse>(&line)
247+
// Try to parse as JSON - if it fails, continue reading
248+
if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
249+
break trimmed.to_string();
250+
} else {
251+
println!("🔍 Line is not valid JSON, continuing: {trimmed}");
252+
continue;
253+
}
254+
};
255+
256+
let response = serde_json::from_str::<JsonRpcResponse>(&trimmed_line)
240257
.map_err(|e| BackendError::SessionInitFailed(format!("Failed to parse response: {e}")))?;
241258

242259
if let Some(error) = &response.error {
@@ -1679,6 +1696,42 @@ mod tests {
16791696
assert_eq!(guard.get("thread-test").unwrap().pid, Some(999));
16801697
}
16811698

1699+
#[test]
1700+
fn test_skip_non_json_lines() {
1701+
// Test that we correctly identify non-JSON lines that should be skipped
1702+
let non_json_lines = vec![
1703+
"Data collection is disabled.",
1704+
"",
1705+
"Warning: Something happened",
1706+
"Loading...",
1707+
"debug: info",
1708+
];
1709+
1710+
for line in non_json_lines {
1711+
// These should be identified as non-JSON and skipped
1712+
let is_json_candidate = !line.trim().is_empty() &&
1713+
(line.trim().starts_with('{') || line.trim().starts_with('['));
1714+
assert!(!is_json_candidate, "Line '{}' should be skipped as non-JSON", line);
1715+
}
1716+
1717+
// Test valid JSON lines
1718+
let json_lines = vec![
1719+
r#"{"jsonrpc": "2.0", "id": 1, "result": {}}"#,
1720+
r#"[{"type": "test"}]"#,
1721+
r#"{"method": "test"}"#,
1722+
];
1723+
1724+
for line in json_lines {
1725+
let is_json_candidate = !line.trim().is_empty() &&
1726+
(line.trim().starts_with('{') || line.trim().starts_with('['));
1727+
assert!(is_json_candidate, "Line '{}' should be considered as potential JSON", line);
1728+
1729+
// Verify it's actually parseable JSON
1730+
assert!(serde_json::from_str::<serde_json::Value>(line.trim()).is_ok(),
1731+
"Line '{}' should be valid JSON", line);
1732+
}
1733+
}
1734+
16821735
#[test]
16831736
fn test_session_manager_stress_add_remove() {
16841737
let manager = SessionManager::new();

0 commit comments

Comments
 (0)