Hmbown/CodeWhale · error
Task manager is shutting down; admission is closed
Error message
Task manager is shutting down; admission is closed
What it means
admit_task_record takes the state lock and a cross-process store transaction, then checks the shutdown cancel token. If the task manager is shutting down, admission is closed: no new (or recovered) task records may be added, because no worker will claim them. This fails loud instead of queueing a task that would never run.
Solutions
- Check the manager's shutdown state (or hold a shutdown broadcast/shutdown_requires flag) before submitting tasks
- Re-submit the task after restart, or persist the intent externally and replay on next startup
- Ensure automation schedulers are stopped before initiating task-manager shutdown
- Handle the error in callers by surfacing 'app is shutting down' rather than retrying immediately
Example fix
// before
mgr.add_task_with_id(req, id).await?;
// after
if mgr.is_shutdown().await {
persist_pending_submission(&req).await?; // replay on next startup
} else {
mgr.add_task_with_id(req, id).await?;
} Defensive patterns
Strategy: try-catch
Try / catch
match mgr.add_task_with_id(req, id).await { Err(e) if e.to_string().contains("shutting down") => defer_submission_until_restart(req), r => r?, } Prevention
- Stop automation schedulers before initiating shutdown
- Gate submissions on a shutdown flag/broadcast from the manager
- Persist deferred submissions durably and replay after restart
When it happens
Trigger: Calling add_task_with_id / admit_task_record (or the staged-recovery path that calls it) after task manager shutdown has begun (cancel_token cancelled) — e.g. a task submitted during app exit or while the Runtime is being torn down.
Common situations: A UI/automation race where a user clicks 'run' while the app is quitting; scheduled automation firing during shutdown; recovery replay racing process termination.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A pinned task provider requires an explicit model
- catalog cache unavailable
- child wall-time budget exhausted
- {}
- Failed to steer turn
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/58b42ba13a57f32d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/task_manager.rs:1771
tool_calls: Vec::new(),
timeline: vec![TaskTimelineEntry {
timestamp: Utc::now(),
kind: "queued".to_string(),
summary: "Task queued".to_string(),
detail_path: None,
}],
};
self.admit_task_record(task, false).await
}
async fn admit_task_record(&self, task: TaskRecord, recover_stage: bool) -> Result<TaskRecord> {
{
let mut state = self.state.lock().await;
let _transaction = self.lock_store().await?;
self.refresh_locked(&mut state)?;
if self.cancel_token.is_cancelled() {
bail!("Task manager is shutting down; admission is closed");
}
if task.execution_scope.as_deref() != Some(self.execution_scope()) {
bail!(
"Task execution scope is unverified or belongs to another Runtime; refusing adoption"
);
}
let task_path = self.tasks_dir.join(format!("{}.json", task.id));
// The staged extension is intentionally not `.json`, so startup
// replay ignores an interrupted create until the queue write has
// succeeded and this file is atomically promoted.
let staged_task_path = self.tasks_dir.join(format!(".{}.json.pending", task.id));
if recover_stage {
if let Some(accepted) = self.read_bound_task(&task.id)? {
validate_bound_task_request(&accepted, &NewTaskRequest::from_task(&task))?;
return Ok(accepted);
}
let current = read_bound_task_file(&staged_task_path, &task.id)?
.context("Unaccepted task stage disappeared during recovery")?;View on GitHub (pinned to 73e0f67d83)