{"record":{"id":"c7e20f88c07062f7","repo":"Hmbown/CodeWhale","slug":"tracked-ssh-fleet-worker-worker-id-has-no-host-a","errorCode":null,"errorMessage":"tracked SSH Fleet worker {worker_id} has no host adapter","messagePattern":"tracked SSH Fleet worker (.+?) has no host adapter","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/executor.rs","lineNumber":646,"sourceCode":"            .and_then(|stream| stream.attempt.clone())\n    }\n\n    /// Stop a tracked worker at the host boundary.\n    ///\n    /// Operator controls run in a separate process from the foreground Fleet\n    /// manager, so they communicate cancellation through the durable ledger.\n    /// The manager calls this method after observing that terminal state; the\n    /// executor is the only owner that can reliably reach the live local/SSH\n    /// adapter handle.\n    pub fn stop_worker(&mut self, worker_id: &str) -> Result<()> {\n        let ssh_key = match self.streams.get(worker_id).map(|stream| &stream.host) {\n            Some(WorkerStreamHost::Local) => None,\n            Some(WorkerStreamHost::Ssh(key)) => Some(key.clone()),\n            None => return Ok(()),\n        };\n        if let Some(key) = ssh_key {\n            let adapter = self.ssh_adapters.get_mut(&key).ok_or_else(|| {\n                anyhow::anyhow!(\"tracked SSH Fleet worker {worker_id} has no host adapter\")\n            })?;\n            adapter.stop_worker(worker_id)?;\n        } else {\n            self.adapter.stop_worker(worker_id)?;\n        }\n        Ok(())\n    }\n\n    /// Stop tracking a terminal worker so the scheduler can reuse the same\n    /// logical worker id for the next queued task.\n    pub fn forget_worker(&mut self, worker_id: &str) {\n        let Some(stream) = self.streams.remove(worker_id) else {\n            return;\n        };\n        match stream.host {\n            WorkerStreamHost::Local => {\n                let _ = self.adapter.cleanup_worker(worker_id);\n            }","sourceCodeStart":628,"sourceCodeEnd":664,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/fleet/executor.rs#L628-L664","documentation":"`stop_worker` checks how a worker runs (fleet/executor.rs:646): the streams map says SSH (a host key is present) but `ssh_adapters` no longer holds an adapter for that key. The executor still tracks the worker yet has lost the handle needed to stop it, so cancellation fails.","triggerScenarios":"The adapters map was cleared or pruned while the worker was still tracked; the adapter entry was removed after a reconnect or partial reset; executor state was rebuilt keeping streams but dropping adapters.","commonSituations":"Lifecycle bugs in custom executors; cleanup paths that drop adapters before forgetting workers; test harnesses constructing partial executor state.","solutions":["Keep each SSH host adapter registered for the lifetime of its workers; remove it only after `forget_worker`","Re-create the adapter for that host key, then retry stop","If the worker already terminated, stop tracking it with `forget_worker` instead"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// before calling stop_worker\nif let Some(stream) = streams.get(worker_id) {\n    if let WorkerStreamHost::Ssh(key) = &stream.host {\n        anyhow::ensure!(\n            ssh_adapters.contains_key(key),\n            \"cannot stop {worker_id}: host adapter for {key} is gone\"\n        );\n    }\n}","typeGuard":null,"tryCatchPattern":"match executor.stop_worker(worker_id) {\n    Err(err) if err.to_string().contains(\"no host adapter\") => {\n        // adapter handle is gone; drop tracking so the id can be reused\n        executor.forget_worker(worker_id);\n    }\n    other => other?,\n}","preventionTips":["Order teardown: stop workers, then forget them, then drop host adapters","Never clear ssh_adapters while streams still track SSH workers","Assert stream/adapter map consistency in executor tests"],"tags":["fleet","ssh","lifecycle","executor","cancellation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}