jdx/mise · error
the first agent request must be hello
Error message
the first agent request must be hello
What it means
handle_connection implements a line-delimited request protocol with a mandatory handshake: the first request on a fresh stream must be Hello (a version mismatch gets a dedicated "cache client X does not match agent Y" Error response first). Any other first message — Lookup, Record, garbage — bails with this error and the connection ends.
Source
Thrown at crates/mise-cache-core/src/agent.rs:1767
},
)
.await?;
return Ok(());
}
AgentRequest::Hello { client_version, .. } => {
send_response(
&mut writer,
&AgentResponse::Error {
message: format!(
"cache client {client_version} does not match agent {}",
self.version
),
},
)
.await?;
return Ok(());
}
_ => bail!("the first agent request must be hello"),
}
send_response(
&mut writer,
&AgentResponse::Hello {
protocol: AGENT_PROTOCOL_VERSION,
agent_version: self.version.to_string(),
},
)
.await?;
while let Some(line) = lines.next_line().await? {
let response = match serde_json::from_str(&line) {
Ok(request) => self.respond(request).await,
Err(error) => AgentResponse::Error {
message: format!("invalid agent request: {error}"),
},
};
send_response(&mut writer, &response).await?;View on GitHub (pinned to 6f52dcdf99)
Solutions
- Send the Hello request as the very first line, matching AgentRequest's serde shape with a client_version field
- Read the Hello response: a version-mismatch Error names both client and agent versions — align them
- Upgrade the client to a build whose protocol matches the agent
Example fix
// before
writeln!(writer, "{}", serde_json::to_string(&AgentRequest::Lookup { .. })?)?;
// after
writeln!(writer, "{}", serde_json::to_string(&AgentRequest::Hello { client_version })?)?;
let _hello = read_response(&mut reader).await?;
writeln!(writer, "{}", serde_json::to_string(&AgentRequest::Lookup { .. })?)?; Defensive patterns
Strategy: validation
Validate before calling
// Always open the session with Hello and consume the reply before any request
let hello = serde_json::to_string(&AgentRequest::Hello { client_version })?;
writeln!(writer, "{hello}")?; writer.flush().await?;
match read_response(&mut reader).await? {
AgentResponse::Hello { agent_version, .. } => { /* check version compatibility */ }
AgentResponse::Error { message } => bail!("handshake rejected: {message}"),
_ => bail!("unexpected first response"),
} Prevention
- Send Hello as the literal first line on every new connection — no exceptions
- Handle the version-mismatch Error response explicitly; it tells you both versions
- Keep point health checks from sending arbitrary bytes to the protocol port
When it happens
Trigger: A client sending requests before Hello; a raw probe (nc/curl) writing a non-Hello line; a client whose serde enum encoding deserializes into the wrong variant; a client skipping handshake by bug.
Common situations: Hand-rolled or out-of-date clients; health checks poking the socket with arbitrary bytes; protocol version skew between client and agent builds.
Related errors
- cannot record a hit for a missing action result
- task action manifest baseline was not loaded
- task action manifest is not canonical JSON
- remote task action manifest changed too frequently
- remote rustc action metadata is invalid
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/f0408db47df30842.
Report an issue: GitHub.