{"record":{"id":"93467c5b346dea5f","repo":"astrid-runtime/astrid","slug":"mcp-gateway-returned-an-unbound-control-acknowledg","errorCode":null,"errorMessage":"MCP gateway returned an unbound control acknowledgement","messagePattern":"MCP gateway returned an unbound control acknowledgement","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/lifecycle.rs","lineNumber":679,"sourceCode":"        .context(\"failed to write MCP gateway control\")?;\n    write_half\n        .write_all(b\"\\n\")\n        .await\n        .context(\"failed to terminate MCP gateway control\")?;\n    write_half\n        .flush()\n        .await\n        .context(\"failed to flush MCP gateway control\")?;\n\n    let mut reader = BufReader::new(read_half);\n    let response = tokio::time::timeout(READY_TIMEOUT, read_bounded_line(&mut reader))\n        .await\n        .context(\"timed out waiting for MCP gateway control acknowledgement\")??;\n    let ack: GatewayControlAck =\n        serde_json::from_slice(&response).context(\"invalid MCP gateway control acknowledgement\")?;\n    if ack.version != GATEWAY_CONTROL_VERSION || ack.operation != operation || ack.pid != record.pid\n    {\n        anyhow::bail!(\"MCP gateway returned an unbound control acknowledgement\");\n    }\n    if !ack.ok {\n        anyhow::bail!(\n            \"MCP gateway rejected {operation:?}: {}\",\n            ack.error.as_deref().unwrap_or(\"unknown gateway failure\")\n        );\n    }\n    Ok(ack)\n}\n\npub(crate) async fn read_bounded_line<R>(reader: &mut R) -> Result<Vec<u8>>\nwhere\n    R: tokio::io::AsyncRead + Unpin,\n{\n    let mut line = Vec::new();\n    for _ in 0..=MAX_CONTROL_BYTES {\n        let byte = reader\n            .read_u8()","sourceCodeStart":661,"sourceCodeEnd":697,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/lifecycle.rs#L661-L697","documentation":"The gateway's control acknowledgement failed binding checks: its protocol version, operation name, or PID didn't match what the client requested (GATEWAY_CONTROL_VERSION, the requested operation, and the ready record's PID). The library treats an unbound ack as untrustworthy and bails rather than acting on it.","triggerScenarios":"request_gateway_control receives an ack where ack.version != GATEWAY_CONTROL_VERSION, ack.operation != operation, or ack.pid != record.pid — typically because a different (old/new) gateway process is answering on the socket.","commonSituations":"Version mismatch between CLI and installed gateway binary; a new gateway replaced the old one on the same socket mid-request; PID reuse or a stale ready record pointing at the wrong process.","solutions":["Re-run gateway startup to regenerate a ready record matching the live gateway process","Ensure the astrid CLI and gateway binaries are the same version (reinstall/upgrade both)","Delete stale gateway ready/marker files so the client binds to the correct PID","Reproduce with logging of the ack fields to identify which binding (version/operation/pid) failed"],"exampleFix":"// before: assume ack matches\nlet ack = request_gateway_control(...).await?;\n// after: surface the mismatch for diagnosis\nmatch request_gateway_control(...).await {\n    Err(e) if e.to_string().contains(\"unbound control acknowledgement\") => {\n        // refresh ready record and retry once\n        refresh_gateway_ready_record()?;\n        request_gateway_control(...).await?;\n    },\n    other => other?,\n}","handlingStrategy":"retry","validationCode":"let record = read_gateway_ready()?;\nanyhow::ensure!(record.pid != 0, \"ready record has no PID; restart gateway\");\nanyhow::ensure!(\n    gateway_version() == GATEWAY_CONTROL_VERSION,\n    \"CLI/gateway control version mismatch\"\n);","typeGuard":"fn ack_is_bound(ack: &GatewayControlAck, op: &str, pid: i32) -> bool {\n    ack.version == GATEWAY_CONTROL_VERSION && ack.operation == op && ack.pid == pid\n}","tryCatchPattern":"match request_gateway_control(...).await {\n    Err(e) if e.to_string().contains(\"unbound control acknowledgement\") => {\n        refresh_gateway_ready_record()?;\n        request_gateway_control(...).await?\n    },\n    other => other?,\n}","preventionTips":["Keep CLI and gateway binaries version-locked","Regenerate ready records after gateway restarts","Treat stale socket/PID state as suspect after any crash"],"tags":["ipc","version-mismatch","protocol"],"backgroundTag":"unexpected-response-shape","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}