libnyanpasu/clash-nyanpasu · error

failed to start download: {e}

Error message

failed to start download: {e}

What it means

Wraps a failure of the download task's `run()` call in `DownloadSession::start`. `run()` initiates the transfer; if the underlying downloader cannot even begin (runtime submit failure, task already finished/cancelled, adapter setup error), the error is re-wrapped as "failed to start download: {e}". This is distinct from a mid-transfer failure, which surfaces later as "download failed".

Source

Thrown at backend/tauri/src/core/download/mod.rs:128

            .adapter(adapter)
            .save_path(save_path)
            .threaded_runtime(ThreadedRuntimeImpl::new_tokio_rt())
            .cancel_token(inner.cancel.clone())
            .on_task_state_changed(move |event| cb_inner.on_event(event))
            .build()
            .await
            .map_err(|e| anyhow::anyhow!("failed to build download task: {e}"))?;
        Ok(Self {
            inner,
            task: Mutex::new(task),
        })
    }

    pub async fn start(&self) -> anyhow::Result<()> {
        let mut task = self.task.lock().await;
        task.run()
            .await
            .map_err(|e| anyhow::anyhow!("failed to start download: {e}"))?;
        match task.wait().await {
            Ok(()) => Ok(()),
            Err(e) => {
                *self.inner.state.write() = DownloaderState::Failed(e.to_string());
                Err(anyhow::anyhow!("download failed: {e}"))
            }
        }
    }

    pub fn status(&self) -> DownloadStatus {
        let prog = *self.inner.progress.read();
        DownloadStatus {
            state: self.inner.state.read().clone(),
            downloaded: prog.downloaded,
            total: prog.total,
            speed: prog.speed,
        }
    }

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Create a fresh `DownloadSession` for each download instead of re-running a finished/failed one.
  2. Do not call `start()` after `cancel()` or after `status()` reports a terminal state; check status first.
  3. Read the wrapped `{e}` to identify the underlying cause (already-run, cancelled, runtime failure).
  4. If transient (runtime spawn failure), retry after freeing resources.

Example fix

// before
session.start().await?;
// after
if let DownloadSession::Failed(_) = session.status().state {
    anyhow::bail!("previous download failed; create a new session");
}
session.start().await.map_err(|e| anyhow!("cannot start download: {e:#}"))?;
Defensive patterns

Strategy: validation

Validate before calling

// check session state before starting
if let DownloaderState::Failed(_) | DownloaderState::Finished = *session.status().state {
    anyhow::bail!("session already terminal; create a new DownloadSession");
}

Try / catch

session.start().await
    .map_err(|e| { log::error!("download could not start: {e:#}"); e })?;

Prevention

When it happens

Trigger: Calling `DownloadSession::start()` and the task's `run()` future returns an error — e.g. starting a task that was already run/completed, the cancel token already fired, or the threaded runtime rejects the spawn.

Common situations: Calling `start()` twice on the same session; calling start after `cancel()`; resource-exhaustion in the runtime; bug in session reuse after a previous terminal state.

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


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/957065f7430eff79. Report an issue: GitHub.