sigoden/aichat · info · anyhow::Error

Aborted.

Error message

Aborted.

What it means

abortable_run_with_spinner_rx also races against wait_abort_signal; when an abort signal has already been (or becomes) set through the shared AbortSignal — e.g. a previous Ctrl+C or a programmatic abort — it bails with 'Aborted.' (note the period), distinguishing signal-based aborts from a fresh Ctrl+C ('Aborted!').

Solutions

  1. Create or reset a fresh AbortSignal for a new run instead of reusing an aborted one
  2. Avoid calling subsequent spinner-wrapped operations after an abort until state is reset
  3. Check is_ctrlc / abort state before launching follow-up work
Defensive patterns

Strategy: try-catch

Validate before calling

// only start a run when the signal isn't already aborted
if abort_signal.is_aborted() {
    // skip or reset the signal before starting the task
}

Try / catch

match abortable_run_with_spinner_rx(task, spinner, signal).await {
    Err(e) if e.to_string() == "Aborted." => {
        eprintln!("operation aborted via abort signal; not a failure of the task itself");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Starting or running a spinner-wrapped task after the shared abort signal was set by an earlier Ctrl+C or by abort_signal.abort(); wait_abort_signal resolves and the task is abandoned.

Common situations: Reusing the same AbortSignal across sequential operations; pressing Ctrl+C once and observing the next operation abort immediately; a nested abortable task inheriting an aborted signal.

Related errors


AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09). Data as JSON: /api/errors/7da8243ccf7663ac. Report an issue: GitHub.

Appendix: source

Thrown at src/utils/spinner.rs:163

where
    F: Future<Output = Result<T>>,
{
    if *IS_STDOUT_TERMINAL {
        let (done_tx, done_rx) = oneshot::channel();
        let run_task = async {
            tokio::select! {
                ret = task => {
                    let _ = done_tx.send(());
                    ret
                }
                _ = tokio::signal::ctrl_c() => {
                    abort_signal.set_ctrlc();
                    let _ = done_tx.send(());
                    bail!("Aborted!")
                },
                _ = wait_abort_signal(&abort_signal) => {
                    let _ = done_tx.send(());
                    bail!("Aborted.");
                },
            }
        };
        let (task_ret, spinner_ret) = tokio::join!(
            run_task,
            run_abortable_spinner(spinner_rx, done_rx, abort_signal.clone())
        );
        spinner_ret?;
        task_ret
    } else {
        task.await
    }
}

async fn run_abortable_spinner(
    mut spinner_rx: UnboundedReceiver<SpinnerEvent>,
    mut done_rx: oneshot::Receiver<()>,
    abort_signal: AbortSignal,

View on GitHub (pinned to 82976d349a)