aaif-goose/goose · warning
Download is being cancelled; wait for it to finish before re
Error message
Download is being cancelled; wait for it to finish before restarting
What it means
Thrown by download_model_sharded_with_bearer_token (and its download_model / download_model_sharded wrappers) when the shared progress map already holds an entry for the same model_id whose status is Cancelled while task_exited is still false. cancel_download() only flips the status flag; the spawned download task notices it at its next checkpoint (between shards, before each HTTP request, every <=500ms of backoff sleep, or after each streamed chunk), deletes .partial files, and only then sets task_exited = true. This error is the guard against racing a restart against an in-flight cancellation.
Source
Thrown at crates/goose-download-manager/src/lib.rs:232
model_id: String,
files: Vec<(String, PathBuf)>,
total_size_hint: u64,
bearer_token: Option<String>,
on_complete: Option<Box<dyn FnOnce() + Send + 'static>>,
) -> Result<()> {
info!(model_id = %model_id, file_count = files.len(), "Starting model download");
{
let mut downloads = self
.downloads
.lock()
.map_err(|_| anyhow::anyhow!("Failed to acquire lock"))?;
if let Some(existing) = downloads.get(&model_id) {
if existing.status == DownloadStatus::Downloading {
anyhow::bail!("Download already in progress");
}
if existing.status == DownloadStatus::Cancelled && !existing.task_exited {
anyhow::bail!(
"Download is being cancelled; wait for it to finish before restarting"
);
}
}
downloads.insert(
model_id.clone(),
DownloadProgress {
model_id: model_id.clone(),
status: DownloadStatus::Downloading,
bytes_downloaded: 0,
total_bytes: total_size_hint,
progress_percent: 0.0,
speed_bps: None,
eta_seconds: None,
error: None,
task_exited: false,
},View on GitHub (pinned to 3810898a74)
Solutions
- Poll get_progress(model_id) until task_exited == true (or the entry leaves the Cancelled state) before calling download again
- Drive the restart off the progress state instead of a fixed sleep after cancel_download
- Only start a new download under a different model_id if you genuinely want an independent concurrent download
Example fix
// before
manager.cancel_download(model_id)?;
manager.download_model(model_id.into(), url, dest, None).await?;
// after: wait for the cancelled task to settle
manager.cancel_download(model_id)?;
while let Some(p) = manager.get_progress(model_id) {
if p.task_exited { break; }
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
manager.download_model(model_id.into(), url, dest, None).await?; Defensive patterns
Strategy: validation
Validate before calling
// check the progress map before restarting a cancelled download
fn can_restart(manager: &DownloadManager, model_id: &str) -> bool {
match manager.get_progress(model_id) {
None => true,
Some(p) => p.task_exited && p.status != DownloadStatus::Downloading,
}
} Try / catch
match manager.download_model_sharded(...).await {
Err(e) if e.to_string().contains("wait for it to finish") => {
// poll get_progress until task_exited, then retry the call once
}
other => other,
} Prevention
- Never restart in the same handler that called cancel_download; wait for task_exited
- Model UI state on the progress map (status + task_exited), not on local assumptions
- Log model_id on every cancel/restart to catch racing callers
When it happens
Trigger: Calling cancel_download(model_id) and immediately calling download_model / download_model_sharded[_with_bearer_token] again with the same model_id before the background task has observed the cancel and set task_exited = true. The window is widest for multi-shard GGUF downloads where checkpoints are far apart.
Common situations: A UI 'Cancel' button handler that auto-restarts the download in the same tick; a retry loop that treats the Cancelled status as restartable without waiting; test code that cancels and restarts back-to-back.
Related errors
- Download cancelled
- state-machine session loaded without conversation
- state machine conversation has no kickoff message
- cannot determine the role of an empty conversation
- Failed to acquire lock
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/1a9bffd6975e11e6.
Report an issue: GitHub.