{"record":{"id":"e4860e611b7bd3f5","repo":"atuinsh/atuin","slug":"failed-to-compute-stats","errorCode":null,"errorMessage":"Failed to compute stats","messagePattern":"Failed to compute stats","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin/src/command/client/wrapped.rs","lineNumber":348,"sourceCode":"            let host_id = Settings::host_id().await?;\n            let alias_store = AliasStore::new(store, host_id, encryption_key);\n\n            alias_store\n                .aliases()\n                .await\n                .unwrap_or_default()\n                .into_iter()\n                .map(|a| (a.name, a.value))\n                .collect()\n        } else {\n            HashMap::new()\n        }\n    } else {\n        HashMap::new()\n    };\n\n    // Compute overall stats using existing functionality\n    let stats = compute(settings, &history, 10, 1).expect(\"Failed to compute stats\");\n    let wrapped_stats = WrappedStats::new(settings, &stats, &history, &alias_map);\n\n    // Print wrapped format\n    print_wrapped_header(year);\n\n    println!(\"🎉 In {year}, you typed {} commands!\", stats.total_commands);\n    println!(\n        \"   That's ~{} commands every day\\n\",\n        stats.total_commands / 365\n    );\n\n    println!(\"Your Top Commands:\");\n    atuin_history::stats::pretty_print(stats.clone(), 1, theme);\n    println!();\n\n    print_fun_facts(&wrapped_stats, &stats, year);\n\n    Ok(())","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/atuinsh/atuin/blob/202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1/crates/atuin/src/command/client/wrapped.rs#L330-L366","documentation":"A panic from `.expect()` on the `Option` returned by `atuin_history::stats::compute` in `atuin wrapped` (wrapped.rs:348). `compute` does not return a `Result` — it returns `None` when, after filtering, `top.is_empty()` (stats.rs:286-288): every command in the supplied history was skipped by `stats.ignored_commands`, collapsed away by `common_prefix`/`interesting_command`, or reduced to an empty string so the `.windows(ngram_size)` n-gram count produced nothing. The empty-history case is already guarded earlier (wrapped.rs:320), so this panic fires only when the year has history entries but zero usable stats.","triggerScenarios":"Running `atuin wrapped <year>` (or the month-appropriate default year) where `db.range(start, end)` returns non-empty history but `compute(settings, &history, 10, 1)` yields `None`: e.g. every command matches an `ignored_commands`/`common_prefix` entry in config.toml, or entries whose command strings are empty (or only env-var prefixes) after `strip_leading_env_vars` and trimming, leaving no n-grams for `windows(1)`.","commonSituations":"An over-broad `stats.ignored_commands` or `common_prefix: [\"*\"]`-style config that filters the entire year; imported shell history containing blank/env-assignment-only entries; a year where the user only ran commands that Atuin's default prefix rules collapse to nothing.","solutions":["Handle the `None` case gracefully instead of `.expect`: use `let Some(stats) = ... else { print a friendly 'no usable commands' message; return Ok(()) }`","Review `stats.ignored_commands` and `common_prefix` in `~/.config/atuin/config.toml` and narrow the patterns","Inspect the year's history for empty or env-only commands: `atuin search --before <end> --after <start> <cmd>` or query the sqlite DB, and clean/purge junk entries","As a library fix, have `wrapped` skip the pretty-print block (or lower the filter) when `compute` returns `None`"],"exampleFix":"// before\nlet stats = compute(settings, &history, 10, 1).expect(\"Failed to compute stats\");\n\n// after\nlet Some(stats) = compute(settings, &history, 10, 1) else {\n    println!(\"No usable commands found for {year} — check 'stats.ignored_commands' and 'common_prefix' in your config\");\n    return Ok(());\n};","handlingStrategy":"fallback","validationCode":"// Before computing, check the year yields at least one usable command\nlet usable = history.iter().any(|h| {\n    let c = atuin_history::stats::strip_leading_env_vars(h.command.trim());\n    !c.is_empty()\n        && !settings.stats.ignored_commands.iter()\n            .any(|ig| ig == &atuin_history::stats::interesting_command(settings, c))\n});\nif !usable {\n    println!(\"No usable commands for {year} — check your stats ignore rules\");\n    return Ok(());\n}","typeGuard":"// compute() itself is the guard — treat it as a checked operation:\nlet stats = match compute(settings, &history, 10, 1) {\n    Some(s) => s,\n    None => return Ok(()), // nothing reportable for this year\n};","tryCatchPattern":"// Rust Option, not exception — use let-else so absence is a normal outcome:\nlet Some(stats) = compute(settings, &history, 10, 1) else {\n    println!(\"No usable commands found for {year}\");\n    return Ok(());\n};","preventionTips":["Never `.expect`/`.unwrap` on Option-returning aggregation functions; empty aggregate results are a legitimate input state, not a bug","Keep `stats.ignored_commands` and `common_prefix` patterns narrow, and remember they also shrink `atuin wrapped`/`atuin stats` output","Purge or fix imported history entries with empty/env-assignment-only commands","When adding features over `compute`, handle `None` explicitly in tests so the fallback branch is covered"],"tags":["rust","atuin","cli","option","unwrap","stats","config","panic"],"backgroundTag":"unwrap-none-panic","analyzedSha":"202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1","analyzedAt":"2026-08-16T19:30:24.731Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}