herdrdev/herdr · error · io::Error
app response channel closed
Error message
app response channel closed
What it means
dispatch_to_app waits on the response channel after forwarding a request to the app. If the app side has dropped the response sender, recv returns Disconnected, mapped to ErrorKind::BrokenPipe with 'app response channel closed'. This means the app (or its handler task) exited or was shut down while the request was outstanding — the server outlived its app counterpart for that channel.
Source
Thrown at src/api/server.rs:859
active.store(false, Ordering::Release);
}
return error_response_json(
request_id,
"server_unavailable",
format!("failed to dispatch request: {err}"),
);
}
let response = match timeout {
Some(timeout) => response_rx.recv_timeout(timeout).map_err(|err| match err {
std::sync::mpsc::RecvTimeoutError::Timeout => std::io::Error::new(
std::io::ErrorKind::TimedOut,
format!(
"timed out waiting for app response after {} ms",
timeout.as_millis()
),
),
std::sync::mpsc::RecvTimeoutError::Disconnected => std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"app response channel closed",
),
}),
None => response_rx
.recv()
.map_err(|err| std::io::Error::new(std::io::ErrorKind::BrokenPipe, err)),
};
match response {
Ok(response) => response,
Err(err) => {
if let Some(active) = request_active {
active.store(false, Ordering::Release);
}
error_response_json(
request_id,
"server_unavailable",View on GitHub (pinned to f457cff4f2)
Solutions
- Treat it as a shutdown signal: reconnect or re-establish the session instead of retrying the same channel.
- Check the app process / server logs for a panic that dropped the channel sender.
- Ensure request handlers always send exactly one response before returning, including on error paths.
- In tests, join the app thread before issuing further API calls, or expect BrokenPipe during teardown windows.
Example fix
// before
match dispatch_to_app(&tx, req, timeout) {
Ok(resp) => { /* ... */ }
Err(e) => panic!("api failed: {e}"),
}
// after
match dispatch_to_app(&tx, req, timeout) {
Ok(resp) => { /* ... */ }
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
// app shut down mid-request; reconnect or exit gracefully
}
Err(e) => return Err(e),
} Defensive patterns
Strategy: fallback
Validate before calling
null
Try / catch
match dispatch_to_app(&tx, req, timeout) {
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {
// app is gone: mark session dead, reconnect or exit; do not retry the same channel
}
other => other,
} Prevention
- Guarantee every app request handler sends exactly one response before exiting, on all error paths.
- Shut down API clients before tearing down the app to avoid in-flight requests.
- Treat BrokenPipe during quit/restart windows as expected, not exceptional.
When it happens
Trigger: The app closes/shuts down while an API request is in flight; the handler task that owns the response sender panics or returns early; dispatch after the app has begun teardown (e.g. during quit) so every receiver of the response mpsc is gone.
Common situations: Client sends a request at the exact moment the user quits the app; a panic in the app's request handler dropping the channel; tests that shut the app down but keep the API server running; races during restart/update flows.
Related errors
- api request line is too large
- timed out reading api request
- timed out waiting for app response after {} ms
- shutdown was requested, but the old remote herdr server on {
- ssh bridge stdin missing
AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28).
Data as JSON: /api/errors/bc747c3ed7ba3853.
Report an issue: GitHub.