astrid-runtime/astrid · error
unsupported MCP gateway control version {}
Error message
unsupported MCP gateway control version {} What it means
Control requests on the gateway socket carry a protocol version field that must equal GATEWAY_CONTROL_VERSION. When a client sends a GatewayRequest::Control whose version differs, read_gateway_request_inner rejects it. This guards the control channel against clients speaking an incompatible protocol revision.
Source
Thrown at crates/astrid-cli/src/commands/mcp/gateway.rs:905
let byte = reader
.read_u8()
.await
.context("failed to read MCP attach registration")?;
if byte == b'\n' {
terminated = true;
break;
}
line.push(byte);
}
if !terminated {
anyhow::bail!("MCP attach registration is missing or too large");
}
let request: GatewayRequest =
serde_json::from_slice(&line).context("MCP gateway registration is not valid JSON")?;
match &request {
GatewayRequest::Control(control) => {
if control.version != GATEWAY_CONTROL_VERSION {
anyhow::bail!(
"unsupported MCP gateway control version {}",
control.version
);
}
if control.pid == 0 || control.hook_token.trim().is_empty() {
anyhow::bail!("MCP gateway control authority is incomplete");
}
},
GatewayRequest::Attach(registration) => validate_registration(registration)?,
}
Ok(request)
}
fn validate_registration(registration: &AttachRegistration) -> Result<()> {
if registration.version != ATTACH_REGISTRATION_VERSION {
anyhow::bail!(
"unsupported MCP attach registration version {}",
registration.versionView on GitHub (pinned to affd8760f4)
Solutions
- Upgrade or downgrade so the client and gateway come from the same build; check `astrid --version` on both sides.
- Restart/kill any stale gateway daemon left from a previous version so a fresh one with matching GATEWAY_CONTROL_VERSION starts.
- Update custom control scripts to use the current GATEWAY_CONTROL_VERSION constant from the codebase.
- Clear stale socket/lease files under daemon_root before reconnecting so the old-version gateway is not reused.
Example fix
// before: custom script pins an old version
{"type":"control","version":0,"pid":1234,"hook_token":"..."}
// after: use the constant the library exports
let req = json!({"type":"control","version":GATEWAY_CONTROL_VERSION,"pid":std::process::id(),"hook_token":token}); Defensive patterns
Strategy: validation
Validate before calling
fn control_version_ok(v: u32, expected: u32) -> bool { v == expected } Try / catch
match result {
Err(e) if e.to_string().contains("unsupported MCP gateway control version") => eprintln!("client/gateway version skew: restart the gateway and match versions"),
other => other?,
} Prevention
- Ship client and gateway from the same build.
- Reference GATEWAY_CONTROL_VERSION instead of hardcoding numbers.
- Restart stale daemons after upgrades.
- Check versions on both ends before opening the control channel.
When it happens
Trigger: Sending a control request (stop/status) with control.version != GATEWAY_CONTROL_VERSION from read_gateway_request/read_registration_inner parsing.
Common situations: Mixed-version CLI and daemon after an upgrade (old astrid binary talking to a newer gateway or vice versa); hand-crafted control scripts with a hardcoded version number; a stale daemon running old code.
Related errors
- expected MCP attach registration
- MCP attach registration is missing or too large
- unsupported MCP attach registration version {}
- MCP gateway attach limit reached for principal '{principal}'
- {primary:#}; additional gateway cleanup failure: {secondary:
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/081f7eb51284f006.
Report an issue: GitHub.