bxctl: escape error details, time out on admin.sock, AdminError::Io

M3a review findings 9, 10, 12, 13. A retrying error and every error detail
can carry the inference server's body, so they are escaped like model text.
Admin requests wait at most 30 s, so a stuck brokerd cannot hang bxctl or a
chat turn. A failed write is AdminError::Io and stops handle_pending instead
of being answered with another write. The usage line says what audit verify
checks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 21:09:53 -07:00
co-authored by Claude Opus 5.5
parent 70d582acf5
commit a75a5453e9
5 changed files with 132 additions and 8 deletions
+38
View File
@@ -0,0 +1,38 @@
//! A `brokerd` that accepts and never answers does not hang `bxctl` (M3a review finding 9).
use std::os::unix::net::UnixListener;
use std::time::{Duration, Instant};
use bxctl::admin::{ADMIN_TIMEOUT, request_with_timeout};
use proto::{Empty, Message};
#[test]
fn the_default_is_thirty_seconds() {
assert_eq!(ADMIN_TIMEOUT, Duration::from_secs(30));
}
#[test]
fn a_silent_brokerd_is_an_error_after_the_timeout() {
let dir = std::env::temp_dir().join(format!("bx-admin-timeout-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let socket = dir.join("admin.sock");
let listener = UnixListener::bind(&socket).unwrap();
let held = std::thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
std::thread::sleep(Duration::from_millis(2_000));
drop(stream);
});
let started = Instant::now();
let got = request_with_timeout(
&socket,
Message::Approvals(Empty {}),
Duration::from_millis(200),
);
let took = started.elapsed();
assert!(got.is_err(), "{got:?}");
assert!(took < Duration::from_millis(1_500), "waited {took:?}");
held.join().unwrap();
let _ = std::fs::remove_dir_all(&dir);
}
+57
View File
@@ -0,0 +1,57 @@
//! Text from `brokerd` or the inference server is escaped like model text before it reaches the
//! terminal: a `retrying` error and every error detail (M3a review finding 10).
use bxctl::admin::AdminError;
use bxctl::chat::{ChatError, Printer};
use proto::{ErrorCode, TurnEvent, WireError};
const ESC: char = '\u{1b}';
fn hostile() -> String {
format!("the server responded with 503: {ESC}[2J{ESC}]0;owned\u{7}\u{202e}")
}
fn clean(text: &str) {
for c in text.chars() {
let cp = u32::from(c);
assert!(
cp >= 0x20 && !(0x7f..=0x9f).contains(&cp) && !(0x2028..=0x202e).contains(&cp)
|| c == '\n',
"raw {cp:#x} in {text:?}"
);
}
}
#[test]
fn a_retrying_error_is_escaped() {
let mut out = Vec::new();
let mut printer = Printer::new(true, false);
printer
.event(
&mut out,
&TurnEvent::Retrying {
attempt: 1,
after_ms: 2000,
error: hostile(),
},
)
.unwrap();
let text = String::from_utf8(out).unwrap();
clean(&text);
assert!(text.contains("\\u001b[2J"), "{text}");
}
#[test]
fn an_error_detail_is_escaped_in_chat_and_admin_errors() {
let wire = WireError {
code: ErrorCode::Inference,
detail: hostile(),
};
for text in [
ChatError::Refused(wire.clone()).to_string(),
AdminError::Refused(wire).to_string(),
] {
clean(&text);
assert!(text.contains("\\u202e"), "{text}");
}
}