Remove all alternate update paths — fleet redeploy is release + upgrade all
Killed: - rebuild_and_restart (in-container cargo self-compile): the MCP tool, the `rebuild` chat command in all four transports, the web-ui bot command, and the underlying function. This was the path that caused the exec() deadlocks. - upgrade_sled gateway MCP tool: second entry point to sled upgrades, defaulted to serving the gateway's own macOS binary to Linux sleds. - GET /api/huskies-binary (both sled and gateway route trees): served current_exe(), wrong platform when the gateway is macOS. Superseded by /api/artifacts/ which now also serves on the gateway route tree. - `huskies upgrade` CLI subcommand and --source flag: third way of doing the same download-and-replace. Escape hatch for a bricked sled is `docker cp` + restart. Kept, distinct jobs: `project-rebuild` (container/image updates), `rebuild gateway` + script/local-release (gateway self-update). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019fHdm92yjvguPi2LiXfLB9
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
use crate::agents::move_story_to_stage;
|
||||
use crate::http::context::AppContext;
|
||||
use crate::log_buffer;
|
||||
use crate::slog;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
mod permission;
|
||||
@@ -43,23 +42,6 @@ pub(crate) fn tool_get_server_logs(args: &Value) -> Result<String, String> {
|
||||
Ok(all_lines[start..].join("\n"))
|
||||
}
|
||||
|
||||
/// Rebuild the server binary and re-exec (delegates to `crate::rebuild`).
|
||||
pub(crate) async fn tool_rebuild_and_restart(ctx: &AppContext) -> Result<String, String> {
|
||||
slog!("[rebuild] Rebuild and restart requested via MCP tool");
|
||||
|
||||
// Signal the Matrix bot (if active) so it can send its own shutdown
|
||||
// announcement before the process is replaced. Best-effort: we wait up
|
||||
// to 1.5 s for the message to be delivered.
|
||||
if let Some(ref tx) = ctx.matrix_shutdown_tx {
|
||||
let _ = tx.send(Some(crate::rebuild::ShutdownReason::Rebuild));
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1500)).await;
|
||||
}
|
||||
|
||||
let project_root = ctx.state.get_project_root().unwrap_or_default();
|
||||
let notifier = ctx.bot_shutdown.as_deref();
|
||||
crate::rebuild::rebuild_and_restart(&ctx.services.agents, &project_root, notifier).await
|
||||
}
|
||||
|
||||
/// MCP tool called by Claude Code via `--permission-prompt-tool`.
|
||||
///
|
||||
/// Forwards the permission request through the shared channel to the active
|
||||
|
||||
@@ -335,57 +335,6 @@ mod tests {
|
||||
assert_eq!(servers[0], "huskies");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rebuild_and_restart_in_tools_list() {
|
||||
use super::super::super::tools_list::handle_tools_list;
|
||||
let resp = handle_tools_list(Some(json!(1)));
|
||||
let tools = resp.result.unwrap()["tools"].as_array().unwrap().clone();
|
||||
let tool = tools.iter().find(|t| t["name"] == "rebuild_and_restart");
|
||||
assert!(
|
||||
tool.is_some(),
|
||||
"rebuild_and_restart missing from tools list"
|
||||
);
|
||||
let t = tool.unwrap();
|
||||
assert!(t["description"].as_str().unwrap().contains("Rebuild"));
|
||||
assert!(t["inputSchema"].is_object());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rebuild_and_restart_kills_agents_before_build() {
|
||||
// Verify that calling rebuild_and_restart on an empty pool doesn't
|
||||
// panic and proceeds to the build step. We can't test exec() in a
|
||||
// unit test, but we can verify the build attempt happens.
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test_ctx(tmp.path());
|
||||
|
||||
// The build will succeed (we're running in the real workspace) and
|
||||
// then exec() will be called — which would replace our test process.
|
||||
// So we only test that the function *runs* without panicking up to
|
||||
// the agent-kill step. We do this by checking the pool is empty.
|
||||
assert_eq!(ctx.services.agents.list_agents().await.unwrap().len(), 0);
|
||||
ctx.services.agents.kill_all_children().await; // should not panic on empty pool
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rebuild_uses_matching_build_profile() {
|
||||
// The build must use the same profile (debug/release) as the running
|
||||
// binary, otherwise cargo build outputs to a different target dir and
|
||||
// current_exe() still points at the old binary.
|
||||
let build_args: Vec<&str> = if cfg!(debug_assertions) {
|
||||
vec!["build", "-p", "huskies"]
|
||||
} else {
|
||||
vec!["build", "--release", "-p", "huskies"]
|
||||
};
|
||||
|
||||
// Tests always run in debug mode, so --release must NOT be present.
|
||||
assert!(
|
||||
!build_args.contains(&"--release"),
|
||||
"In debug builds, rebuild must not pass --release (would put \
|
||||
the binary in target/release/ while current_exe() points to \
|
||||
target/debug/)"
|
||||
);
|
||||
}
|
||||
|
||||
// ── move_story tool tests ─────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -81,8 +81,6 @@ pub async fn dispatch_tool_call(
|
||||
// Diagnostics
|
||||
"get_server_logs" => diagnostics::tool_get_server_logs(&args),
|
||||
"get_version" => diagnostics::tool_get_version(ctx),
|
||||
// Server lifecycle
|
||||
"rebuild_and_restart" => diagnostics::tool_rebuild_and_restart(ctx).await,
|
||||
// Permission bridge (Claude Code → frontend dialog)
|
||||
"prompt_permission" => diagnostics::tool_prompt_permission(&args, ctx).await,
|
||||
// Token usage
|
||||
|
||||
@@ -78,7 +78,6 @@ mod tests {
|
||||
assert!(names.contains(&"get_server_logs"));
|
||||
assert!(names.contains(&"prompt_permission"));
|
||||
assert!(names.contains(&"get_pipeline_status"));
|
||||
assert!(names.contains(&"rebuild_and_restart"));
|
||||
assert!(names.contains(&"get_token_usage"));
|
||||
assert!(names.contains(&"move_story"));
|
||||
assert!(names.contains(&"unblock_story"));
|
||||
@@ -117,7 +116,7 @@ mod tests {
|
||||
assert!(names.contains(&"convert_item_type"));
|
||||
assert!(names.contains(&"edit"));
|
||||
assert!(names.contains(&"write"));
|
||||
assert_eq!(tools.len(), 85);
|
||||
assert_eq!(tools.len(), 84);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -42,14 +42,6 @@ pub(super) fn system_tools() -> Vec<Value> {
|
||||
"properties": {}
|
||||
}
|
||||
}),
|
||||
json!({
|
||||
"name": "rebuild_and_restart",
|
||||
"description": "Rebuild the server binary from source and re-exec with the new binary. Gracefully stops all running agents before restart. If the build fails, the server stays up and returns the build error.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}),
|
||||
json!({
|
||||
"name": "prompt_permission",
|
||||
"description": "Present a permission request to the user via the web UI. Used by Claude Code's --permission-prompt-tool to delegate permission decisions to the frontend dialog. Returns on approval; returns an error on denial.",
|
||||
|
||||
Reference in New Issue
Block a user