{"record":{"id":"3c4bbdf828e9bc1b","repo":"t8y2/dbx","slug":"tdengine-response-writer-stopped","errorCode":null,"errorMessage":"TDengine response writer stopped","messagePattern":"TDengine response writer stopped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/tdengine/src/runtime.rs","lineNumber":97,"sourceCode":"        }\n        Ok::<(), anyhow::Error>(())\n    });\n\n    let mut lines = BufReader::new(tokio::io::stdin()).lines();\n    let mut requests = JoinSet::new();\n    while let Some(line) = lines.next_line().await? {\n        while requests.try_join_next().is_some() {}\n        let line = line.trim();\n        if line.is_empty() {\n            continue;\n        }\n        let parsed = serde_json::from_str::<RpcRequest>(line);\n        if parsed.as_ref().is_ok_and(|request| request.method == \"shutdown\") {\n            let response = match parsed {\n                Ok(request) => handle_request(runtime.clone(), request).await,\n                Err(error) => error_response(Value::Null, \"request\", None, error.into()),\n            };\n            responses.send(response).map_err(|_| anyhow!(\"TDengine response writer stopped\"))?;\n            while requests.join_next().await.is_some() {}\n            break;\n        }\n        let request_permit = if parsed.as_ref().is_ok_and(|request| is_capacity_exempt(&request.method)) {\n            None\n        } else {\n            match runtime.request_slots.clone().try_acquire_owned() {\n                Ok(permit) => Some(permit),\n                Err(_) => {\n                    let response = match parsed {\n                        Ok(request) => error_response(\n                            if request.id.is_null() { json!(1) } else { request.id },\n                            &request.method,\n                            session_id(&request.params),\n                            anyhow!(\"agent request capacity is temporarily exhausted\"),\n                        ),\n                        Err(error) => error_response(Value::Null, \"request\", None, error.into()),\n                    };","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/tdengine/src/runtime.rs#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check that the host process keeps the agent's stdout pipe open until after it reads the shutdown response","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'","Ensure the agent process is not closed before the client consumes responses; drain responses before exiting the parent","If the parent intentionally exits, treat this error as expected teardown noise rather than a bug"],"exampleFix":"// before (host closes agent stdout immediately after sending shutdown)\nchild.stdin.write_all(b\"{\\\"method\\\":\\\"shutdown\\\"}\\n\");\ndrop(child.stdout);\n// after (drain stdout until process exits)\nchild.stdin.write_all(b\"{\\\"method\\\":\\\"shutdown\\\"}\\n\");\nlet output = child.wait_with_output()?; // keep stdout open and read it","handlingStrategy":"try-catch","validationCode":"const child = spawn(agentCmd, { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\nif (!child.stdout || child.stdout.destroyed) throw new Error(\"agent stdout pipe unavailable\");","typeGuard":"function hasLiveStdout(child) {\n  return child?.stdout != null && !child.stdout.destroyed && child.exitCode === null;\n}","tryCatchPattern":"try {\n  await agent.run();\n} catch (e) {\n  if (\"TDengine response writer stopped\".length && /response writer (stopped|task failed)/.test(String(e.cause ?? e))) {\n    // stdout pipe closed: restart the agent and re-handshake\n    await restartAgent();\n  } else { throw e; }\n}","preventionTips":["Always keep a reader attached to agent stdout for the agent's entire lifetime; never drop/close it early","Read the shutdown response before terminating or closing the pipe to the agent","Monitor the child's stderr and exit events so a dead writer is detected immediately","Treat any writer-stopped error as fatal to the agent loop: restart the process rather than reusing it"],"tags":["io","ipc","tdengine","broken-pipe","stdout"],"backgroundTag":"broken-pipe","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}