refactor: surface silent fallbacks with eprintln! logging

Add eprintln! logging before fallback values in 7 files where errors
were previously swallowed without visibility:

- [stream] malformed JSON chunk, missing usage tokens, missing tool call index
- [mcp] missing result fields, client builder failure, response body read error
- [subagent] missing API key in settings, all resolution paths exhausted
- [state] memory_dir no-parent, session_id missing, lock poisoned, store_base_dir
- [input] missing default_model for provider
- [session] corrupt agents.json parse failure
- [pkce] clock-before-epoch on random byte generation

All fallback values are preserved — this adds observability without
changing behavior for callers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
asepharyana
2026-07-12 10:50:34 +07:00
co-authored by Claude Opus 4.8
parent bb621fdff2
commit e29dfadaa7
7 changed files with 97 additions and 25 deletions
+28 -7
View File
@@ -94,7 +94,10 @@ impl StdioChild {
if let Some(err) = resp.get("error") {
anyhow::bail!("MCP error: {}", err);
}
return Ok(resp.get("result").cloned().unwrap_or(Value::Null));
return Ok(resp.get("result").cloned().unwrap_or_else(|| {
eprintln!("[mcp] stdio response missing 'result' field: {}", trimmed);
Value::Null
}));
}
}
Err(e) => anyhow::bail!("MCP stdio read error: {}", e),
@@ -186,7 +189,10 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
.timeout(std::time::Duration::from_millis(MCP_CALL_TIMEOUT_MS))
.connect_timeout(std::time::Duration::from_millis(MCP_CONNECT_TIMEOUT_MS))
.build()
.unwrap_or_else(|_| reqwest::blocking::Client::new());
.unwrap_or_else(|e| {
eprintln!("[mcp] HTTP client builder failed: {}, using default client without timeouts", e);
reqwest::blocking::Client::new()
});
let request_id: u64 = 1;
let body = json!({
@@ -207,7 +213,10 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
if !resp.status().is_success() {
let status = resp.status();
let text = resp.text().unwrap_or_default();
let text = resp.text().unwrap_or_else(|e| {
eprintln!("[mcp] failed to read HTTP response body: {}", e);
String::new()
});
anyhow::bail!("MCP HTTP server returned {}: {}", status, text);
}
@@ -218,7 +227,10 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
anyhow::bail!("MCP HTTP error: {}", err);
}
let result = response.get("result").cloned().unwrap_or(Value::Null);
let result = response.get("result").cloned().unwrap_or_else(|| {
eprintln!("[mcp] HTTP response missing 'result' field");
Value::Null
});
extract_text_content(&result)
}
@@ -237,7 +249,10 @@ fn extract_text_content(result: &Value) -> anyhow::Result<String> {
}
}
}
Ok(serde_json::to_string_pretty(result).unwrap_or_else(|_| result.to_string()))
Ok(serde_json::to_string_pretty(result).unwrap_or_else(|e| {
eprintln!("[mcp] failed to pretty-print result: {}", e);
result.to_string()
}))
}
#[derive(Debug, Clone)]
@@ -320,8 +335,14 @@ impl McpManager {
tool_list.iter().filter_map(|t| {
Some(McpToolInfo {
name: t.get("name")?.as_str()?.to_string(),
description: t.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string(),
input_schema: t.get("inputSchema").cloned().unwrap_or(serde_json::Value::Null),
description: t.get("description").and_then(|v| v.as_str()).unwrap_or_else(|| {
eprintln!("[mcp] tool {} missing description", t.get("name").and_then(|n| n.as_str()).unwrap_or("?"));
""
}).to_string(),
input_schema: t.get("inputSchema").cloned().unwrap_or_else(|| {
eprintln!("[mcp] tool {} missing inputSchema", t.get("name").and_then(|n| n.as_str()).unwrap_or("?"));
serde_json::Value::Null
}),
})
}).collect()
} else {