benfred/py-spy · error

invalid duration

Error message

invalid duration

What it means

This panic comes from parsing the --duration CLI option for the `record` subcommand. py-spy accepts "unlimited" or a number of seconds; any other value is parsed with u64::from_str and the failed parse panics with "invalid duration" via expect().

Source

Thrown at src/config.rs:395

        let mut config = Config::default();

        let (subcommand, matches) = matches.subcommand().unwrap();

        // Check if `--native` was used on an unsupported platform
        if subcommand != "completions" && !cfg!(feature = "unwind") && matches.get_flag("native") {
            eprintln!(
                "Collecting stack traces from native extensions (`--native`) is not supported on your platform."
            );
            std::process::exit(1);
        }

        match subcommand {
            "record" => {
                config.sampling_rate = *matches.get_one("rate").unwrap();
                config.duration = match matches.get_one::<String>("duration").map(|d| d.as_str()) {
                    Some("unlimited") | None => RecordDuration::Unlimited,
                    Some(seconds) => {
                        RecordDuration::Seconds(seconds.parse().expect("invalid duration"))
                    }
                };
                config.format = matches.get_one("format").copied();
                config.filename = matches.get_one::<String>("output").cloned();
                config.show_line_numbers = !matches.get_flag("nolineno");
                config.lineno = if matches.get_flag("nolineno") {
                    LineNo::NoLine
                } else if matches.get_flag("function") {
                    LineNo::First
                } else {
                    LineNo::LastInstruction
                };
                config.include_thread_ids = matches.get_flag("threads");
                if matches.get_flag("nolineno") && matches.get_flag("function") {
                    eprintln!("--function & --nolinenos can't be used together");
                    std::process::exit(1);
                }
                config.hide_progress = matches.get_flag("hideprogress");

View on GitHub (pinned to 32080cc0c2)

Solutions

  1. Pass the duration as plain seconds: `py-spy record --duration 300` instead of `--duration 5m`.
  2. Use `--duration unlimited` (or omit --duration) for open-ended recording, and stop the process with Ctrl-C to write the output.
  3. Convert minutes/hours to seconds in your wrapper script before invoking py-spy.
  4. Patch config.rs to parse duration suffixes if you build py-spy from source.

Example fix

// before
py-spy record --duration 5m -o profile.svg --pid 1234
// after
py-spy record --duration 300 -o profile.svg --pid 1234
Defensive patterns

Strategy: validation

Validate before calling

case "$DURATION" in
  unlimited|'') ;;
  *[!0-9]*|[!0-9]*) echo "--duration must be 'unlimited' or plain seconds" >&2; exit 1 ;;
esac

Prevention

When it happens

Trigger: Running `py-spy record --duration <value>` where value is neither "unlimited" nor omitted and does not parse as an unsigned integer (e.g. "30s", "1m", "0.5", "ten").

Common situations: Users pass human-friendly durations like "--duration 5m" or "--duration 30s" out of habit from tools like timeout or fluentd; or wrap the number in quotes/units by mistake in scripts.

Related errors


AI-assisted analysis of benfred/py-spy@32080cc0c2 (2026-09-05). Data as JSON: /api/errors/035c3f77fc7b2e75. Report an issue: GitHub.