mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(core_process): demote expected port-clash + Windows bind ACL to warn (#2B, #AT, #BV, #BT) (#1628)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Claude Opus 4.7
Steven Enamakel
parent
737314e7b6
commit
57b4e0e3b8
@@ -172,7 +172,11 @@ impl CoreProcessHandle {
|
||||
"Core RPC port {} is in use by something that is not an OpenHuman core ({reason}). Refusing to attach (set OPENHUMAN_CORE_REUSE_EXISTING=1 to override) — quit the other process or set OPENHUMAN_CORE_PORT to a different port and relaunch.",
|
||||
self.port
|
||||
);
|
||||
log::error!("[core] {msg}");
|
||||
if is_expected_port_clash(&reason) {
|
||||
log::warn!("[core] {msg}");
|
||||
} else {
|
||||
log::error!("[core] {msg}");
|
||||
}
|
||||
return Err(msg);
|
||||
}
|
||||
}
|
||||
@@ -236,7 +240,11 @@ impl CoreProcessHandle {
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::error!("[core] embedded core server exited with error: {e}");
|
||||
if is_expected_port_clash(&e.to_string()) {
|
||||
log::warn!("[core] embedded core server exited with error: {e}");
|
||||
} else {
|
||||
log::error!("[core] embedded core server exited with error: {e}");
|
||||
}
|
||||
} else {
|
||||
log::info!("[core] embedded core server exited cleanly");
|
||||
}
|
||||
@@ -380,14 +388,19 @@ impl CoreProcessHandle {
|
||||
self.shutdown().await;
|
||||
|
||||
if !had_managed_task && self.is_rpc_port_open().await {
|
||||
log::error!(
|
||||
let msg = format!(
|
||||
"Core RPC port {} is already in use by another process (OpenHuman did not start it). Quit any `openhuman-core run` in a terminal or set OPENHUMAN_CORE_PORT to a different port, then relaunch the app.",
|
||||
self.port
|
||||
);
|
||||
// Precondition check: by the time we hit this branch we already
|
||||
// know the port is held by something OpenHuman did not spawn, so
|
||||
// the clash is always benign environment state — no need to gate
|
||||
// through `is_expected_port_clash`.
|
||||
log::warn!(
|
||||
"[core] restart: nothing to stop but port {} is in use — another process owns it",
|
||||
self.port
|
||||
);
|
||||
return Err(format!(
|
||||
"Core RPC port {} is already in use by another process (OpenHuman did not start it). Quit any `openhuman-core run` in a terminal or set OPENHUMAN_CORE_PORT to a different port, then relaunch the app.",
|
||||
self.port
|
||||
));
|
||||
return Err(msg);
|
||||
}
|
||||
|
||||
const POLL_MS: u64 = 50;
|
||||
@@ -586,6 +599,20 @@ fn is_openhuman_root_body(body: &str) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns true when a port conflict is deterministic environment state, not
|
||||
/// a high-signal unknown squatter worth sending to Sentry at error level.
|
||||
fn is_expected_port_clash(reason: &str) -> bool {
|
||||
let reason = reason.to_ascii_lowercase();
|
||||
reason.contains("error sending request for url")
|
||||
|| reason.contains("connection refused")
|
||||
|| reason.contains("returned status 404")
|
||||
|| reason.contains("returned status 200")
|
||||
|| reason.contains("body did not identify as openhuman")
|
||||
|| reason.contains("already in use by another process")
|
||||
|| reason.contains("os error 10013")
|
||||
|| reason.contains("wsaeacces")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn find_pid_on_port(port: u16) -> Option<u32> {
|
||||
let output = std::process::Command::new("lsof")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
current_rpc_token, default_core_port, generate_rpc_token, is_openhuman_root_body,
|
||||
parse_lsof_pid, parse_netstat_pid, CoreProcessHandle,
|
||||
current_rpc_token, default_core_port, generate_rpc_token, is_expected_port_clash,
|
||||
is_openhuman_root_body, parse_lsof_pid, parse_netstat_pid, CoreProcessHandle,
|
||||
};
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
|
||||
@@ -127,6 +127,43 @@ fn is_openhuman_root_body_rejects_other_services() {
|
||||
assert!(!is_openhuman_root_body(r#"{"name": 42}"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_port_clash_classifier_matches_benign_probe_shapes() {
|
||||
assert!(is_expected_port_clash(
|
||||
"probe GET / failed: error sending request for url (http://127.0.0.1:7788/)"
|
||||
));
|
||||
assert!(is_expected_port_clash(
|
||||
"probe GET / failed: connection refused"
|
||||
));
|
||||
assert!(is_expected_port_clash(
|
||||
"probe GET / returned status 404 Not Found"
|
||||
));
|
||||
assert!(is_expected_port_clash("probe GET / returned status 200 OK"));
|
||||
assert!(is_expected_port_clash(
|
||||
"probe GET / body did not identify as openhuman (\"hello\")"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_port_clash_classifier_matches_windows_acl_bind_shapes() {
|
||||
assert!(is_expected_port_clash(
|
||||
"Failed to bind to 127.0.0.1:7788: access denied (os error 10013)"
|
||||
));
|
||||
assert!(is_expected_port_clash(
|
||||
"Failed to bind to 127.0.0.1:7788: WSAEACCES"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_port_clash_classifier_rejects_unknown_probe_shapes() {
|
||||
assert!(!is_expected_port_clash(
|
||||
"probe GET / failed: TLS handshake failed: protocol error"
|
||||
));
|
||||
assert!(!is_expected_port_clash(
|
||||
"probe GET / body read failed: unexpected eof"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_lsof_pid_picks_first_pid() {
|
||||
assert_eq!(parse_lsof_pid("12345\n"), Some(12345));
|
||||
|
||||
Reference in New Issue
Block a user