Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.

Commit 784f07a

Browse files
committed
Harden worker boot recovery before task dispatch
The worker boot registry now exposes the requested lifecycle states, emits structured trust and prompt-delivery events, and recovers from shell or wrong-target prompt delivery by replaying the last prompt. Supporting fixes keep MCP remote config parsing backwards-compatible and make CLI argument parsing less dependent on ambient config and cwd state so the workspace stays green under full parallel test runs. Constraint: Worker prompts must not be dispatched before a confirmed ready_for_prompt handshake Constraint: Prompt misdelivery recovery must stay minimal and avoid new dependencies Rejected: Keep prompt_accepted and blocked as public lifecycle states | user requested the narrower explicit state set Rejected: Treat url-only MCP server configs as invalid | existing CLI/runtime tests still rely on that shorthand Confidence: high Scope-risk: moderate Reversibility: clean Directive: Preserve prompt_in_flight semantics when extending worker boot; misdelivery detection depends on it Tested: cargo build --workspace; cargo test --workspace Not-tested: Live tmux worker delivery against a real external coding agent pane
1 parent d87fbe6 commit 784f07a

6 files changed

Lines changed: 398 additions & 88 deletions

File tree

rust/crates/runtime/src/config.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,8 @@ fn parse_mcp_server_config(
786786
context: &str,
787787
) -> Result<McpServerConfig, ConfigError> {
788788
let object = expect_object(value, context)?;
789-
let server_type = optional_string(object, "type", context)?.unwrap_or("stdio");
789+
let server_type =
790+
optional_string(object, "type", context)?.unwrap_or_else(|| infer_mcp_server_type(object));
790791
match server_type {
791792
"stdio" => Ok(McpServerConfig::Stdio(McpStdioServerConfig {
792793
command: expect_string(object, "command", context)?.to_string(),
@@ -818,6 +819,14 @@ fn parse_mcp_server_config(
818819
}
819820
}
820821

822+
fn infer_mcp_server_type(object: &BTreeMap<String, JsonValue>) -> &'static str {
823+
if object.contains_key("url") {
824+
"http"
825+
} else {
826+
"stdio"
827+
}
828+
}
829+
821830
fn parse_mcp_remote_server_config(
822831
object: &BTreeMap<String, JsonValue>,
823832
context: &str,
@@ -1297,6 +1306,41 @@ mod tests {
12971306
fs::remove_dir_all(root).expect("cleanup temp dir");
12981307
}
12991308

1309+
#[test]
1310+
fn infers_http_mcp_servers_from_url_only_config() {
1311+
let root = temp_dir();
1312+
let cwd = root.join("project");
1313+
let home = root.join("home").join(".claw");
1314+
fs::create_dir_all(&home).expect("home config dir");
1315+
fs::create_dir_all(&cwd).expect("project dir");
1316+
fs::write(
1317+
home.join("settings.json"),
1318+
r#"{
1319+
"mcpServers": {
1320+
"remote": {
1321+
"url": "https://example.test/mcp"
1322+
}
1323+
}
1324+
}"#,
1325+
)
1326+
.expect("write mcp settings");
1327+
1328+
let loaded = ConfigLoader::new(&cwd, &home)
1329+
.load()
1330+
.expect("config should load");
1331+
1332+
let remote_server = loaded.mcp().get("remote").expect("remote server should exist");
1333+
assert_eq!(remote_server.transport(), McpTransport::Http);
1334+
match &remote_server.config {
1335+
McpServerConfig::Http(config) => {
1336+
assert_eq!(config.url, "https://example.test/mcp");
1337+
}
1338+
other => panic!("expected http config, got {other:?}"),
1339+
}
1340+
1341+
fs::remove_dir_all(root).expect("cleanup temp dir");
1342+
}
1343+
13001344
#[test]
13011345
fn parses_plugin_config_from_enabled_plugins() {
13021346
let root = temp_dir();

rust/crates/runtime/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ pub use usage::{
143143
format_usd, pricing_for_model, ModelPricing, TokenUsage, UsageCostEstimate, UsageTracker,
144144
};
145145
pub use worker_boot::{
146-
Worker, WorkerEvent, WorkerEventKind, WorkerFailure, WorkerFailureKind, WorkerReadySnapshot,
147-
WorkerRegistry, WorkerStatus,
146+
Worker, WorkerEvent, WorkerEventKind, WorkerEventPayload, WorkerFailure, WorkerFailureKind,
147+
WorkerPromptTarget, WorkerReadySnapshot, WorkerRegistry, WorkerStatus,
148+
WorkerTrustResolution,
148149
};
149150

150151
#[cfg(test)]

rust/crates/runtime/src/mcp_stdio.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,8 @@ impl McpServerManagerError {
360360
}
361361

362362
fn recoverable(&self) -> bool {
363-
matches!(self, Self::Transport { .. } | Self::Timeout { .. })
363+
!matches!(self.lifecycle_phase(), McpLifecyclePhase::InitializeHandshake)
364+
&& matches!(self, Self::Transport { .. } | Self::Timeout { .. })
364365
}
365366

366367
fn discovery_failure(&self, server_name: &str) -> McpDiscoveryFailure {

0 commit comments

Comments
 (0)