Skip to content

Commit 4b3e880

Browse files
chubes4Chris Huber
andauthored
Resolve runner-owned runtime pins before local validation (#11918)
* fix(agent-task): resolve runner-owned runtime pins AI assistance: OpenAI gpt-5.6-sol via OpenCode was used to implement, review, and verify this change. Chris Huber reviewed and is responsible for every line. * fix(test): isolate runner pin resolution from transport AI assistance: OpenAI gpt-5.6-sol via OpenCode was used to diagnose the Candidate Test shard failures, implement, and verify this change. Chris Huber reviewed and is responsible for every line. --------- Co-authored-by: Chris Huber <chris@chubes.net>
1 parent 053e60a commit 4b3e880

1 file changed

Lines changed: 128 additions & 38 deletions

File tree

crates/homeboy-cli/src/cli_runtime.rs

Lines changed: 128 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -979,23 +979,7 @@ fn delegate_agent_task_lifecycle_to_pinned_runtime(
979979
let Some(run_id) = run_id else {
980980
return Ok(None);
981981
};
982-
let Some(pinned) = crate::agents::agent_tasks::lifecycle::pinned_runtime_for_mutation(run_id)?
983-
else {
984-
return Ok(None);
985-
};
986-
let status = ProcessCommand::new(&pinned)
987-
.args(&normalized_args[1..])
988-
.status()
989-
.map_err(|error| {
990-
homeboy::core::Error::internal_io(
991-
error.to_string(),
992-
Some(format!(
993-
"execute pinned controller runtime {}",
994-
pinned.display()
995-
)),
996-
)
997-
})?;
998-
Ok(Some(status.code().unwrap_or(1)))
982+
delegate_agent_task_lifecycle_to_resolved_runtime(run_id, normalized_args)
999983
}
1000984

1001985
fn delegate_cook_continue_to_pinned_runtime(
@@ -1007,31 +991,59 @@ fn delegate_cook_continue_to_pinned_runtime(
1007991
if current_runtime_owns_terminal_cook_continuation(&run_id)? {
1008992
return Ok(None);
1009993
}
994+
delegate_agent_task_lifecycle_to_resolved_runtime(&run_id, normalized_args)
995+
}
996+
997+
enum AgentTaskLifecyclePinnedRuntime {
998+
Runner(crate::agents::agent_tasks::lifecycle::RunnerPinnedRuntime),
999+
Controller(std::path::PathBuf),
1000+
}
1001+
1002+
/// Select runner authority before validating a controller-local pin. Historical
1003+
/// runner records carry paths that are intentionally unavailable on controller.
1004+
fn agent_task_lifecycle_pinned_runtime_for_mutation(
1005+
run_id: &str,
1006+
) -> homeboy::core::Result<Option<AgentTaskLifecyclePinnedRuntime>> {
10101007
if let Some(pinned) =
1011-
crate::agents::agent_tasks::lifecycle::runner_pinned_runtime_for_mutation(&run_id)?
1008+
crate::agents::agent_tasks::lifecycle::runner_pinned_runtime_for_mutation(run_id)?
10121009
{
1013-
return delegate_cook_continue_to_runner_pinned_runtime(&pinned, normalized_args).map(Some);
1010+
return Ok(Some(AgentTaskLifecyclePinnedRuntime::Runner(pinned)));
1011+
}
1012+
Ok(
1013+
crate::agents::agent_tasks::lifecycle::pinned_runtime_for_mutation(run_id)?
1014+
.map(AgentTaskLifecyclePinnedRuntime::Controller),
1015+
)
1016+
}
1017+
1018+
fn delegate_agent_task_lifecycle_to_resolved_runtime(
1019+
run_id: &str,
1020+
normalized_args: &[String],
1021+
) -> homeboy::core::Result<Option<i32>> {
1022+
match agent_task_lifecycle_pinned_runtime_for_mutation(run_id)? {
1023+
Some(AgentTaskLifecyclePinnedRuntime::Runner(pinned)) => {
1024+
delegate_agent_task_lifecycle_to_runner_pinned_runtime(&pinned, normalized_args)
1025+
.map(Some)
1026+
}
1027+
Some(AgentTaskLifecyclePinnedRuntime::Controller(pinned)) => {
1028+
let status = ProcessCommand::new(&pinned)
1029+
.args(&normalized_args[1..])
1030+
.status()
1031+
.map_err(|error| {
1032+
homeboy::core::Error::internal_io(
1033+
error.to_string(),
1034+
Some(format!(
1035+
"execute pinned controller runtime {}",
1036+
pinned.display()
1037+
)),
1038+
)
1039+
})?;
1040+
Ok(Some(status.code().unwrap_or(1)))
1041+
}
1042+
None => Ok(None),
10141043
}
1015-
let Some(pinned) = crate::agents::agent_tasks::lifecycle::pinned_runtime_for_mutation(&run_id)?
1016-
else {
1017-
return Ok(None);
1018-
};
1019-
let status = ProcessCommand::new(&pinned)
1020-
.args(&normalized_args[1..])
1021-
.status()
1022-
.map_err(|error| {
1023-
homeboy::core::Error::internal_io(
1024-
error.to_string(),
1025-
Some(format!(
1026-
"execute pinned controller runtime {}",
1027-
pinned.display()
1028-
)),
1029-
)
1030-
})?;
1031-
Ok(Some(status.code().unwrap_or(1)))
10321044
}
10331045

1034-
fn delegate_cook_continue_to_runner_pinned_runtime(
1046+
fn delegate_agent_task_lifecycle_to_runner_pinned_runtime(
10351047
pinned: &crate::agents::agent_tasks::lifecycle::RunnerPinnedRuntime,
10361048
normalized_args: &[String],
10371049
) -> homeboy::core::Result<i32> {
@@ -1044,14 +1056,36 @@ fn delegate_cook_continue_to_runner_pinned_runtime(
10441056
raw_exec: true,
10451057
..Default::default()
10461058
},
1047-
)?;
1059+
)
1060+
.map_err(|error| annotate_runner_pinned_runtime_failure(error, &pinned.runner_id))?;
10481061
if !output.stderr.is_empty() {
10491062
eprint!("{}", output.stderr);
10501063
}
10511064
print!("{}", output.stdout);
10521065
Ok(exit_code)
10531066
}
10541067

1068+
fn annotate_runner_pinned_runtime_failure(
1069+
mut error: homeboy::core::Error,
1070+
runner_id: &str,
1071+
) -> homeboy::core::Error {
1072+
error.message = format!(
1073+
"runner `{runner_id}` could not execute its pinned controller runtime: {}",
1074+
error.message
1075+
);
1076+
if !error.details.is_object() {
1077+
error.details = serde_json::json!({});
1078+
}
1079+
error.details["runner_id"] = serde_json::json!(runner_id);
1080+
error.details["next_actions"] = serde_json::json!([
1081+
format!("homeboy runner status {runner_id}"),
1082+
format!("homeboy runner connect {runner_id}")
1083+
]);
1084+
error.with_hint(format!(
1085+
"Verify runner `{runner_id}` is reachable, then retry the exact durable command."
1086+
))
1087+
}
1088+
10551089
/// A terminal recipe-bound continuation is controller-owned. Provider execution
10561090
/// remains pinned, but harvest, artifact hydration, gates, and finalization must
10571091
/// run where the immutable Cook recipe is stored rather than on Lab.
@@ -3203,6 +3237,62 @@ mod tests {
32033237
);
32043238
}
32053239

3240+
#[test]
3241+
fn run_delegates_a_runner_owned_linux_v2_pin_before_controller_validation() {
3242+
crate::test_support::with_isolated_home(|_| {
3243+
let run_id = "runner-owned-linux-v2-run";
3244+
let plan: crate::agents::agent_tasks::scheduler::AgentTaskPlan = serde_json::from_str(
3245+
include_str!("../../../tests/fixtures/agent_task_smoke_plan.json"),
3246+
)
3247+
.expect("deserialize durable test plan");
3248+
crate::agents::agent_tasks::lifecycle::submit_plan(&plan, Some(run_id))
3249+
.expect("persist durable run");
3250+
crate::agents::agent_tasks::lifecycle::record_detached_lab_run(
3251+
crate::agents::agent_tasks::lifecycle::DetachedLabRunRecord {
3252+
run_id,
3253+
runner_id: "homeboy-lab",
3254+
runner_job_id: "linux-v2-pin-job",
3255+
remote_workspace: "/home/chubes/reaped-lab-workspace",
3256+
remote_command: &[
3257+
"homeboy".to_string(),
3258+
"agent-task".to_string(),
3259+
"run".to_string(),
3260+
],
3261+
},
3262+
)
3263+
.expect("persist runner authority");
3264+
crate::agents::agent_tasks::lifecycle::rewrite_record_for_test(run_id, |record| {
3265+
record.metadata[homeboy::core::controller_runtime::CONTROLLER_RUNTIME_METADATA_KEY] =
3266+
serde_json::json!({
3267+
"schema": "homeboy/controller-runtime-pin/v2",
3268+
"originating": {
3269+
"build_identity": "homeboy linux-runner",
3270+
"pinned_executable": "/home/chubes/.local/share/homeboy/controller-runtimes/linux/homeboy",
3271+
"sha256": "linux-runner-sha256"
3272+
}
3273+
});
3274+
})
3275+
.expect("persist runner-owned v2 pin");
3276+
3277+
let runtime = agent_task_lifecycle_pinned_runtime_for_mutation(run_id)
3278+
.expect("runner authority is selected before controller-local validation")
3279+
.expect("runner-owned v2 pin selects a runtime");
3280+
assert!(matches!(
3281+
runtime,
3282+
AgentTaskLifecyclePinnedRuntime::Runner(ref pinned)
3283+
if pinned.runner_id == "homeboy-lab"
3284+
&& pinned.executable
3285+
== std::path::Path::new("/home/chubes/.local/share/homeboy/controller-runtimes/linux/homeboy")
3286+
));
3287+
assert_eq!(
3288+
crate::agents::agent_tasks::lifecycle::status(run_id)
3289+
.expect("runtime resolution leaves the durable record intact")
3290+
.run_id,
3291+
run_id
3292+
);
3293+
});
3294+
}
3295+
32063296
#[cfg(unix)]
32073297
#[test]
32083298
fn terminal_cook_with_a_controller_recipe_does_not_route_to_lab_or_origin_runtime() {

0 commit comments

Comments
 (0)