Skip to content

Commit ce7db02

Browse files
authored
fix(status): surface global health from unattached contexts (#11920)
Fixes #11910 AI assistance: OpenAI gpt-5.6-sol via OpenCode inspected the status contract, implemented the structured global-health discovery, and added deterministic coverage. Chris Huber remains responsible for every line.
1 parent 4b3e880 commit ce7db02

3 files changed

Lines changed: 32 additions & 4 deletions

File tree

crates/homeboy-cli/src/commands/status/context_paths.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use serde_json::Value;
1010

1111
use homeboy::core::{component, git};
1212

13-
use super::types::UnregisteredContextStatusOutput;
13+
use super::types::{UnregisteredContextStatusOutput, UnregisteredControlPlaneStatus};
1414

1515
pub(super) fn unregistered_cwd_status_output() -> Option<UnregisteredContextStatusOutput> {
1616
let cwd = std::env::current_dir().ok()?;
@@ -46,7 +46,11 @@ pub(super) fn unregistered_cwd_status_output() -> Option<UnregisteredContextStat
4646
cwd: cwd.to_string_lossy().to_string(),
4747
git_root: git_root_string,
4848
suggestion,
49-
action: "Run `homeboy status --all` to inspect every configured component, or attach this checkout to a project/component first.",
49+
action: "Run `homeboy status --global` for bounded local runner/control-plane health, `homeboy status --all` to inspect every configured component, or attach this checkout to a project/component first.",
50+
control_plane: UnregisteredControlPlaneStatus {
51+
status: "not_checked",
52+
command: "homeboy status --global",
53+
},
5054
})
5155
}
5256

crates/homeboy-cli/src/commands/status/mod.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ pub use types::{
3636
GlobalActivityStatus, GlobalDaemonStatus, GlobalInventoryStatus, GlobalRunnerStatus,
3737
GlobalStatusOutput, ProjectComponentDashboardStatus, ProjectDashboardOutput,
3838
ProjectDashboardSummary, ProjectStatusRow, StatusArgs, StatusOutput, StatusResult,
39-
StatusTiming, UnregisteredContextStatusOutput, UnreleasedMerge, UpstreamDrift,
39+
StatusTiming, UnregisteredContextStatusOutput, UnregisteredControlPlaneStatus, UnreleasedMerge,
40+
UpstreamDrift,
4041
};
4142
use types::{StatusProgress, StatusTimer, READY_TO_DEPLOY_NOTE, UNRELEASED_MERGES_NOTE};
4243

@@ -117,7 +118,11 @@ pub fn run(args: StatusArgs) -> CmdResult<StatusResult> {
117118
"Repo not attached. Prefer: `homeboy project components attach-path <project-id> <path>`"
118119
.to_string()
119120
}),
120-
action: "Run `homeboy status --all` to inspect every configured component, or attach this checkout to a project/component first.",
121+
action: "Run `homeboy status --global` for bounded local runner/control-plane health, `homeboy status --all` to inspect every configured component, or attach this checkout to a project/component first.",
122+
control_plane: UnregisteredControlPlaneStatus {
123+
status: "not_checked",
124+
command: "homeboy status --global",
125+
},
121126
}),
122127
0,
123128
));
@@ -820,6 +825,10 @@ mod tests {
820825
fn global_status_from_an_unregistered_cwd_is_local_and_bounded() {
821826
let _guard = CWD_LOCK.get_or_init(|| Mutex::new(())).lock().unwrap();
822827
crate::test_support::with_isolated_home(|_| {
828+
// A configured runner with no session is disconnected. The global
829+
// follow-up must expose that persisted fact without contacting it.
830+
runner::create(r#"{"id":"disconnected","kind":"local"}"#, false)
831+
.expect("register disconnected runner");
823832
let original_cwd = env::current_dir().expect("current dir");
824833
let dir = TempDir::new().expect("tempdir");
825834
env::set_current_dir(dir.path()).expect("set unregistered cwd");
@@ -842,6 +851,8 @@ mod tests {
842851
assert_eq!(output.inventory.projects, 0);
843852
assert_eq!(output.inventory.components, 0);
844853
assert_eq!(output.runners.inspected, output.runners.registered);
854+
assert!(output.runners.registered >= 1);
855+
assert!(output.runners.disconnected >= 1);
845856
assert_eq!(output.activity.active, 0);
846857
assert!(output.drill_down.contains(&"homeboy daemon status"));
847858
});
@@ -1182,6 +1193,9 @@ mod tests {
11821193
dir.path().canonicalize().ok()
11831194
);
11841195
assert!(output.suggestion.contains("attach"));
1196+
assert_eq!(output.control_plane.status, "not_checked");
1197+
assert_eq!(output.control_plane.command, "homeboy status --global");
1198+
assert!(output.action.contains("homeboy status --global"));
11851199
assert!(output.action.contains("homeboy status --all"));
11861200
}
11871201
_ => panic!("expected unregistered context output"),

crates/homeboy-cli/src/commands/status/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,16 @@ pub struct UnregisteredContextStatusOutput {
196196
pub git_root: Option<String>,
197197
pub suggestion: String,
198198
pub action: &'static str,
199+
/// The default unregistered-context path intentionally avoids even local
200+
/// control-plane inventory reads. This makes that omission explicit and
201+
/// gives callers the bounded, local-only follow-up command.
202+
pub control_plane: UnregisteredControlPlaneStatus,
203+
}
204+
205+
#[derive(Debug, Serialize)]
206+
pub struct UnregisteredControlPlaneStatus {
207+
pub status: &'static str,
208+
pub command: &'static str,
199209
}
200210

201211
/// Bounded, local-only control-plane status. This intentionally contains counts

0 commit comments

Comments
 (0)