mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
fix(server): preserve LLM error source chain in doctor output
`fabro_llm::Error`'s Display only renders the top-level message field for `Network`/`Stream`/`Configuration`/`RequestTimeout` variants — the `#[source]` chain is dropped. Walk the chain at the rendering boundary so connectivity failures (DNS, connection refused, TLS) surface their underlying cause in `fabro doctor` output. Per docs/internal/error-handling-strategy.md, CLI surfaces should render the full cause chain. Adds a regression test that walks `err.source()` on a typed Network error with an inner io::Error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
558e585985
commit
8dcb8ebe7e
1 changed files with 31 additions and 2 deletions
|
|
@ -121,11 +121,12 @@ async fn check_llm_providers(state: &AppState) -> CheckResult {
|
|||
match probe_result {
|
||||
Ok(Ok(())) => details.push(CheckDetail::new(format!("{provider}: OK"))),
|
||||
Ok(Err(err)) => {
|
||||
let rendered = render_error_chain(&err);
|
||||
failures.push(ProviderFailure {
|
||||
name: provider.to_string(),
|
||||
short: short_error_line(&err.to_string()),
|
||||
short: short_error_line(&rendered),
|
||||
});
|
||||
details.push(CheckDetail::new(format!("{provider}: {err}")));
|
||||
details.push(CheckDetail::new(format!("{provider}: {rendered}")));
|
||||
}
|
||||
Err(_) => {
|
||||
failures.push(ProviderFailure {
|
||||
|
|
@ -188,6 +189,17 @@ fn short_error_line(rendered: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_error_chain(err: &dyn std::error::Error) -> String {
|
||||
let mut out = err.to_string();
|
||||
let mut source = err.source();
|
||||
while let Some(cause) = source {
|
||||
out.push_str(": ");
|
||||
out.push_str(&cause.to_string());
|
||||
source = cause.source();
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn probe_model(provider: Provider) -> String {
|
||||
Catalog::builtin()
|
||||
.probe_for_provider(provider)
|
||||
|
|
@ -675,6 +687,23 @@ mod tests {
|
|||
assert_eq!(short_error_line(&input), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_error_chain_includes_underlying_cause_for_typed_llm_error() {
|
||||
let inner = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "tcp connect");
|
||||
let err = fabro_llm::Error::network("openai request failed", inner);
|
||||
|
||||
let rendered = render_error_chain(&err);
|
||||
|
||||
assert!(
|
||||
rendered.contains("openai request failed"),
|
||||
"top-level message missing: {rendered}"
|
||||
);
|
||||
assert!(
|
||||
rendered.contains("tcp connect"),
|
||||
"underlying cause missing — chain not walked: {rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_error_line_returns_first_non_empty_trimmed_line() {
|
||||
let input = " first line \nsecond line";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue