Hmbown/CodeWhale · error
tracked SSH Fleet worker {worker_id} has no host adapter
Error message
tracked SSH Fleet worker {worker_id} has no host adapter What it means
`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.
Source
Thrown at crates/tui/src/fleet/executor.rs:646
.and_then(|stream| stream.attempt.clone())
}
/// Stop a tracked worker at the host boundary.
///
/// Operator controls run in a separate process from the foreground Fleet
/// manager, so they communicate cancellation through the durable ledger.
/// The manager calls this method after observing that terminal state; the
/// executor is the only owner that can reliably reach the live local/SSH
/// adapter handle.
pub fn stop_worker(&mut self, worker_id: &str) -> Result<()> {
let ssh_key = match self.streams.get(worker_id).map(|stream| &stream.host) {
Some(WorkerStreamHost::Local) => None,
Some(WorkerStreamHost::Ssh(key)) => Some(key.clone()),
None => return Ok(()),
};
if let Some(key) = ssh_key {
let adapter = self.ssh_adapters.get_mut(&key).ok_or_else(|| {
anyhow::anyhow!("tracked SSH Fleet worker {worker_id} has no host adapter")
})?;
adapter.stop_worker(worker_id)?;
} else {
self.adapter.stop_worker(worker_id)?;
}
Ok(())
}
/// Stop tracking a terminal worker so the scheduler can reuse the same
/// logical worker id for the next queued task.
pub fn forget_worker(&mut self, worker_id: &str) {
let Some(stream) = self.streams.remove(worker_id) else {
return;
};
match stream.host {
WorkerStreamHost::Local => {
let _ = self.adapter.cleanup_worker(worker_id);
}View on GitHub (pinned to 8880682c63)
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
Defensive patterns
Strategy: validation
Validate before calling
// before calling stop_worker
if let Some(stream) = streams.get(worker_id) {
if let WorkerStreamHost::Ssh(key) = &stream.host {
anyhow::ensure!(
ssh_adapters.contains_key(key),
"cannot stop {worker_id}: host adapter for {key} is gone"
);
}
} Try / catch
match executor.stop_worker(worker_id) {
Err(err) if err.to_string().contains("no host adapter") => {
// adapter handle is gone; drop tracking so the id can be reused
executor.forget_worker(worker_id);
}
other => other?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Lifecycle bugs in custom executors; cleanup paths that drop adapters before forgetting workers; test harnesses constructing partial executor state.
Related errors
- MCP SSE connect cancelled before authentication completed
- SSE transport cancelled before endpoint was discovered
- Runtime API capability '${options.capability}' is not availa
- Runtime API request failed (${response.status}) for ${method
- fleet alert adapter {} is not configured
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/c7e20f88c07062f7.
Report an issue: GitHub.