@smbcloud / cli / commits / 4d005aa

fix: report ControlKit runner metadata probe failures

Seto Elkahfi committed Aug 25, 2026 at 13:55 UTC 4d005aa5ee24bd6cdd6827ed4604e69433506ce0
1 file changed +53 -15
crates/xcrs/src/xcrs.rs
+53 -15
@@ -134,21 +134,34 @@ impl ControlKit {
134 let body = self.send(method, params).await?;
135
136 if let Some(error) = body.get("error") {
137 - let runner_info = if error.get("code").and_then(serde_json::Value::as_i64)
138 - == Some(CONTROLKIT_METHOD_NOT_FOUND)
139 - && method != "device.info"
140 - {
141 - self.send("device.info", serde_json::json!({}))
142 - .await
143 - .ok()
144 - .and_then(|body| body.get("result").cloned())
145 - } else {
146 - None
147 - };
137 + let (runner_info, runner_info_error) =
138 + if error.get("code").and_then(serde_json::Value::as_i64)
139 + == Some(CONTROLKIT_METHOD_NOT_FOUND)
140 + && method != "device.info"
141 + {
142 + match self.send("device.info", serde_json::json!({})).await {
143 + Ok(body) => match body.get("result") {
144 + Some(result) => (Some(result.clone()), None),
145 + None => {
146 + let reason = body
147 + .get("error")
148 + .map(|error| format!("device.info returned {error}"))
149 + .unwrap_or_else(|| {
150 + "device.info response did not contain a result".to_string()
151 + });
152 + (None, Some(reason))
153 + }
154 + },
155 + Err(error) => (None, Some(error.to_string())),
156 + }
157 + } else {
158 + (None, None)
159 + };
160 return Err(anyhow!(controlkit_rpc_error(
161 method,
162 error,
151 - runner_info.as_ref()
163 + runner_info.as_ref(),
164 + runner_info_error.as_deref()
165 )));
166 }
167
@@ -190,6 +203,7 @@ fn controlkit_rpc_error(
203 method: &str,
204 error: &serde_json::Value,
205 runner_info: Option<&serde_json::Value>,
206 + runner_info_error: Option<&str>,
207 ) -> String {
208 let code = error
209 .get("code")
@@ -207,15 +221,19 @@ fn controlkit_rpc_error(
221 let runner = runner_info
222 .and_then(|info| info.get("runner"))
223 .and_then(serde_json::Value::as_str)
210 - .unwrap_or("unknown ControlKit runner");
224 + .unwrap_or("The connected ControlKit runner");
225 let protocol = runner_info
226 .and_then(|info| info.get("protocolVersion"))
227 .and_then(serde_json::Value::as_u64)
228 .map(|version| format!(" protocol {version}"))
229 .unwrap_or_default();
230
231 + let runner_info_note = runner_info_error
232 + .map(|error| format!(" Runner metadata could not be read: {error}."))
233 + .unwrap_or_default();
234 +
235 format!(
218 - "{runner}{protocol} does not implement ControlKit method '{method}'. Rebuild or upgrade xcrs-controlkit, restart its runner, and retry."
236 + "{runner}{protocol} does not implement ControlKit method '{method}'.{runner_info_note} Rebuild or upgrade xcrs-controlkit, restart its runner, and retry."
237 )
238 }
239
@@ -1343,13 +1361,33 @@ mod tests {
1361 "protocolVersion": 1
1362 });
1363
1346 - let message = controlkit_rpc_error("device.dump.ui", &error, Some(&runner_info));
1364 + let message = controlkit_rpc_error("device.dump.ui", &error, Some(&runner_info), None);
1365
1366 assert!(message.contains("XCRSControlKit protocol 1"));
1367 assert!(message.contains("device.dump.ui"));
1368 assert!(message.contains("Rebuild or upgrade"));
1369 }
1370
1371 + #[test]
1372 + fn reports_when_controlkit_runner_metadata_is_unavailable() {
1373 + let error = serde_json::json!({
1374 + "code": -32601,
1375 + "message": "Method not found"
1376 + });
1377 +
1378 + let message = controlkit_rpc_error(
1379 + "device.dump.ui",
1380 + &error,
1381 + None,
1382 + Some("device.info returned a JSON-RPC error"),
1383 + );
1384 +
1385 + assert!(message.contains("The connected ControlKit runner"));
1386 + assert!(message.contains("Runner metadata could not be read"));
1387 + assert!(message.contains("device.info returned a JSON-RPC error"));
1388 + assert!(!message.contains("unknown ControlKit runner"));
1389 + }
1390 +
1391 #[test]
1392 fn extracts_identified_visible_controlkit_elements() {
1393 let hierarchy = serde_json::json!({