zeroclaw-labs/zeroclaw · error
WeCom WS {command} response channel closed before ack (req_i
Error message
WeCom WS {command} response channel closed before ack (req_id={req_id}) What it means
Commands sent over the WeCom WS embed a req_id and register a oneshot response channel in pending_responses. If the sender half of that channel is dropped before the ack arrives — connection task shutting down, entry removed on reconnect, or a req_id collision — the receiver errors with RecvError and this bail fires, naming the command and req_id.
Source
Thrown at crates/zeroclaw-channels/src/wecom_ws.rs:410
&self,
frame: Value,
req_id: &str,
command: &str,
) -> Result<()> {
let (tx, rx) = tokio::sync::oneshot::channel();
self.pending_responses
.lock()
.await
.insert(req_id.to_string(), tx);
if let Err(err) = self.ws_send_frame(frame).await {
self.pending_responses.lock().await.remove(req_id);
return Err(err);
}
match tokio::time::timeout(Duration::from_secs(WECOM_COMMAND_TIMEOUT_SECS), rx).await {
Ok(Ok(result)) => result,
Ok(Err(_)) => anyhow::bail!(
"WeCom WS {command} response channel closed before ack (req_id={req_id})"
),
Err(_) => {
self.pending_responses.lock().await.remove(req_id);
anyhow::bail!(
"WeCom WS {command} ack timeout after {}s (req_id={req_id})",
WECOM_COMMAND_TIMEOUT_SECS
);
}
}
}
async fn maybe_handle_command_response(&self, frame: &Value) -> bool {
let Some(req_id) = frame
.get("headers")
.and_then(|headers| headers.get("req_id"))
.and_then(Value::as_str)
else {View on GitHub (pinned to 88bb9c8533)
Solutions
- Retry the command — a fresh req_id is generated and re-registered on the (new) connection
- During deliberate shutdown, cancel/ignore in-flight commands instead of treating the closed channel as an error
- If it repeats outside reconnects, grep logs for the req_id to check for id collisions or premature cleanup
Defensive patterns
Strategy: retry
Try / catch
Catch the 'response channel closed before ack' bail and re-issue the command (fresh req_id) after confirming the connection is up; suppress and cancel in-flight commands during deliberate shutdown instead of propagating.
Prevention
- Cancel in-flight command futures on reconnect and re-issue them once the session is back
- Serialize WS commands per connection to avoid req_id races
- Log req_id correlation so collisions and premature cleanups are visible
When it happens
Trigger: ws_send_respond_msg or send_markdown_chunks_to_scope in flight exactly when the WS connection shuts down or reconnects; the reader task clears pending_responses while a command awaits its ack; duplicate req_id registration evicting a live waiter.
Common situations: Reconnect racing an outbound command; deliberate channel shutdown with commands still awaiting acks.
Related errors
- WeCom WS {command} ack timeout after {}s (req_id={req_id})
- WeCom WebSocket channel requires the `channel-wecom-ws` feat
- wecom_ws channel is not connected
- QQ WebSocket connection closed: invalid session (fresh auth
- QQ WebSocket connection closed: close_code={code}, reason="{
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3ab707f9d999d3d7.
Report an issue: GitHub.