t8y2/dbx · error

TDengine response writer stopped

Error message

TDengine response writer stopped

What it means

The runtime reads newline-delimited JSON-RPC requests from stdin and forwards responses over an unbounded mpsc channel to a writer task that serializes them to stdout. This error is returned when responses.send() fails because the receiver side (the stdout writer task) is no longer alive — i.e. the writer task has already exited (e.g. it errored writing to stdout or stdout was closed). The runtime then propagates this error out of run(), terminating the agent process loop.

Source

Thrown at agents/drivers/tdengine/src/runtime.rs:97

        }
        Ok::<(), anyhow::Error>(())
    });

    let mut lines = BufReader::new(tokio::io::stdin()).lines();
    let mut requests = JoinSet::new();
    while let Some(line) = lines.next_line().await? {
        while requests.try_join_next().is_some() {}
        let line = line.trim();
        if line.is_empty() {
            continue;
        }
        let parsed = serde_json::from_str::<RpcRequest>(line);
        if parsed.as_ref().is_ok_and(|request| request.method == "shutdown") {
            let response = match parsed {
                Ok(request) => handle_request(runtime.clone(), request).await,
                Err(error) => error_response(Value::Null, "request", None, error.into()),
            };
            responses.send(response).map_err(|_| anyhow!("TDengine response writer stopped"))?;
            while requests.join_next().await.is_some() {}
            break;
        }
        let request_permit = if parsed.as_ref().is_ok_and(|request| is_capacity_exempt(&request.method)) {
            None
        } else {
            match runtime.request_slots.clone().try_acquire_owned() {
                Ok(permit) => Some(permit),
                Err(_) => {
                    let response = match parsed {
                        Ok(request) => error_response(
                            if request.id.is_null() { json!(1) } else { request.id },
                            &request.method,
                            session_id(&request.params),
                            anyhow!("agent request capacity is temporarily exhausted"),
                        ),
                        Err(error) => error_response(Value::Null, "request", None, error.into()),
                    };

View on GitHub (pinned to c0390bff16)

Solutions

  1. Check that the host process keeps the agent's stdout pipe open until after it reads the shutdown response
  2. Look for an earlier writer-task failure (e.g. broken pipe/EPIPE writing '{"ready":true}' or a response) in logs; the writer error is surfaced later via 'TDengine response writer task failed'
  3. Ensure the agent process is not closed before the client consumes responses; drain responses before exiting the parent
  4. If the parent intentionally exits, treat this error as expected teardown noise rather than a bug

Example fix

// before (host closes agent stdout immediately after sending shutdown)
child.stdin.write_all(b"{\"method\":\"shutdown\"}\n");
drop(child.stdout);
// after (drain stdout until process exits)
child.stdin.write_all(b"{\"method\":\"shutdown\"}\n");
let output = child.wait_with_output()?; // keep stdout open and read it
Defensive patterns

Strategy: try-catch

Validate before calling

const child = spawn(agentCmd, { stdio: ["pipe", "pipe", "pipe"] });
if (!child.stdout || child.stdout.destroyed) throw new Error("agent stdout pipe unavailable");

Type guard

function hasLiveStdout(child) {
  return child?.stdout != null && !child.stdout.destroyed && child.exitCode === null;
}

Try / catch

try {
  await agent.run();
} catch (e) {
  if ("TDengine response writer stopped".length && /response writer (stopped|task failed)/.test(String(e.cause ?? e))) {
    // stdout pipe closed: restart the agent and re-handshake
    await restartAgent();
  } else { throw e; }
}

Prevention

When it happens

Trigger: The writer task spawned at runtime.rs:70 panicked or returned Err (for example stdout write/flush failed because the parent process closed the pipe), the channel's receiver was dropped, or the agent's stdout was redirected to a consumer that exited while a 'shutdown' response was still being sent.

Common situations: Parent/host process killed or closed the agent's stdout pipe while the agent was still finishing work; EPIPE when stdout is a pipe whose reader died; a host bug that spawned the agent without a live stdout consumer; the agent outliving its host during teardown.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/3c4bbdf828e9bc1b. Report an issue: GitHub.