astrid-runtime/astrid · error
daemon closed the response stream before the final marker
Error message
daemon closed the response stream before the final marker
What it means
Raised in drain_until_final when the daemon closes the socket (read_message returns Ok(None)) before emitting the message carrying the final marker. It indicates the response stream terminated prematurely rather than by error message or final completion.
Solutions
- Check daemon logs/crash dumps for the reason the stream ended early
- Restart the daemon and retry the job
- Align client and daemon versions so the final-marker protocol matches
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: confirm daemon is alive and protocol version matches let ok = client.status_check().await.is_ok();
Try / catch
match drain_until_final(&mut client, &session).await { Err(e) if e.to_string().contains("before the final marker") => { eprintln!("daemon died mid-response; check daemon logs and retry"); restart_daemon_and_retry().await }, other => other } Prevention
- Run the daemon under a supervisor with restart + logging
- Keep client and daemon versions in lockstep
- Watch for OOM/panics in daemon logs when running large jobs
When it happens
Trigger: Daemon process crashed or was killed mid-turn, daemon hit a panic while processing the job, socket closed unexpectedly by the OS or an intermediate supervisor, or a daemon version that never emits the final marker for this job type.
Common situations: OOM-killed daemon during a large job, daemon restart initiated by a supervisor while a job was in flight, protocol mismatch between an old client and newer daemon.
Related errors
- daemon rejected status request
- daemon returned an unexpected status response
- detached FUSE service exceeded the startup response size
- FSKit callback probe correlation mismatch
- FSKit callback probe failed
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/03f13dbddbfc97d6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/agent/spawn.rs:209
reason: Some("spawn".to_string()),
},
session.0,
);
let _ = client.send_message(disconnect).await;
result
}
/// Read response events until the terminal `AgentResponse { is_final: true }`,
/// accumulating text. Approval requests are auto-DENIED: a locked-down
/// throwaway drafts a result for review, it never acts in the world.
async fn drain_until_final(client: &mut SocketClient, session: &SessionId) -> Result<String> {
let mut response = String::new();
loop {
let message = match client.read_message().await {
Ok(Some(msg)) => msg,
Ok(None) => {
return Err(anyhow!(
"daemon closed the response stream before the final marker"
));
},
Err(e) => return Err(e.context("failed to read from daemon")),
};
match &message.payload {
astrid_types::ipc::IpcPayload::AgentResponse { text, is_final, .. } => {
response.push_str(text);
if *is_final {
break;
}
},
astrid_types::ipc::IpcPayload::ApprovalRequired { request_id, .. } => {
let deny = astrid_types::ipc::IpcPayload::ApprovalResponse {
request_id: request_id.clone(),
decision: "deny".to_string(),
reason: Some("spawn: locked-down throwaway never acts".to_string()),
};View on GitHub (pinned to affd8760f4)