jdx/mise · error

took longer than {}s

Error message

took longer than {}s

What it means

`run_with_limits` runs a command inside a shell with a hard timeout. If the command hasn't finished when `elapsed >= timeout`, mise kills the shell and child, then bails with `took longer than {N}s`. A descendant that outlived the shell is additionally given only a bounded output grace rather than waiting forever.

Source

Thrown at src/system/history/describe_command.rs:167

    let stdout = child.stdout.take().expect("piped");
    let (sender, receiver) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let mut out = Vec::new();
        let _ = stdout.take(DIFF_LIMIT as u64 + 1).read_to_end(&mut out);
        let _ = sender.send(out);
    });
    let started = Instant::now();
    let status = loop {
        if let Some(status) = child.try_wait()? {
            break status;
        }
        if started.elapsed() >= timeout {
            // the shell and whatever it started; the reader thread ends
            // with the last writer of the pipe, so it is not waited for
            active.0.kill();
            let _ = child.kill();
            let _ = child.wait();
            bail!("took longer than {}s", timeout.as_secs());
        }
        std::thread::sleep(Duration::from_millis(100));
    };
    // a descendant that outlived the shell and kept the pipe is not the
    // shell's answer: the output is waited for a moment, not forever
    let Ok(output) = receiver.recv_timeout(output_grace) else {
        active.0.kill();
        bail!("a process it started kept its output open");
    };
    if output.len() > DIFF_LIMIT {
        bail!("description output exceeded {} bytes", DIFF_LIMIT);
    }
    if !status.success() {
        bail!("exited with {status}");
    }
    let Some(line) = first_line(&output) else {
        return Ok(None);
    };

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Increase the configured timeout for this command in your history/describe configuration.
  2. Make the command faster or asynchronous (cache results, skip network calls).
  3. Fix the underlying hang (e.g. a prompt or daemon child that keeps the pipe open).
  4. Set the timeout via the relevant env/config knob (e.g. MISE_HISTORY_* timeout settings) if available.

Example fix

# before: describe command that times out
[history.describe]
command = "curl -s https://slow-api.example.com/status"
# after: bound it and raise the timeout
timeout = 30
[history.describe]
command = "curl -s --max-time 10 https://slow-api.example.com/status"
Defensive patterns

Strategy: retry

Validate before calling

start=$(date +%s); your_slow_command; end=$(date +%s); [ $((end-start)) -lt 30 ] || echo 'will exceed timeout, raise it'

Try / catch

match result {
    Err(e) if e.to_string().contains("took longer than") => retry_with_longer_timeout(cmd),
    other => other?,
}

Prevention

When it happens

Trigger: The command passed to the describe/run-with-limits helper runs longer than the configured timeout (in seconds); the poll loop notices the deadline, kills the process group, and returns this error.

Common situations: A describe-command in config invokes a slow script (network calls, package installs) that exceeds the default timeout; a hung child keeps the pipe open causing follow-up output-grace behavior.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/af86319dab6db2a5. Report an issue: GitHub.