{"record":{"id":"b99823278ad9a62c","repo":"atuinsh/atuin","slug":"issue-in-stats-average-query","errorCode":null,"errorMessage":"issue in stats average query","messagePattern":"issue in stats average query","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-client/src/database.rs","lineNumber":953,"sourceCode":"            .group_by(\"day_of_week\");\n\n        // Intentionally format the string with 01 hardcoded. We want the average runtime for the\n        // _entire month_, but will later parse it as a datetime for sorting\n        // Sqlite has no datetime so we cannot do it there, and otherwise sorting will just be a\n        // string sort, which won't be correct.\n        let mut duration_over_time = SqlBuilder::select_from(\"history\");\n        duration_over_time\n            .fields(&[\n                \"strftime('01-%m-%Y', ROUND(timestamp / 1000000000), 'unixepoch') AS month_year\",\n                \"avg(duration) as duration\",\n            ])\n            .and_where(\"command = ?1\")\n            .group_by(\"month_year\")\n            .having(\"duration > 0\");\n\n        let prev = prev.sql().expect(\"issue in stats previous query\");\n        let next = next.sql().expect(\"issue in stats next query\");\n        let total = total.sql().expect(\"issue in stats average query\");\n        let average = average.sql().expect(\"issue in stats previous query\");\n        let exits = exits.sql().expect(\"issue in stats exits query\");\n        let day_of_week = day_of_week.sql().expect(\"issue in stats day of week query\");\n        let duration_over_time = duration_over_time\n            .sql()\n            .expect(\"issue in stats duration over time query\");\n\n        // The queries are all independent, so run them concurrently on the pool.\n        let (prev, next, total, average, exits, day_of_week, duration_over_time): (\n            _,\n            _,\n            (i64,),\n            (f64,),\n            Vec<(i64, i64)>,\n            Vec<(String, i64)>,\n            Vec<(String, f64)>,\n        ) = tokio::try_join!(\n            sqlx::query(sqlx::AssertSqlSafe(prev))","sourceCodeStart":935,"sourceCodeEnd":971,"githubUrl":"https://github.com/atuinsh/atuin/blob/202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1/crates/atuin-client/src/database.rs#L935-L971","documentation":"The third stats expect() panic (database.rs:953), attached to the 'total' builder. Note the copy-paste quirk in this block: the message says 'issue in stats average query' but the line renders `total.sql()` — so if this exact panic fires, the broken builder is the totals query, despite the wording. As with the siblings, it is an internal assertion over a hard-coded query shape and is unreachable through user data or configuration.","triggerScenarios":"Running `atuin stats` on a build where the 'total' builder construction was broken; the misleading message means you should look at the `total` builder, not `average`, when debugging.","commonSituations":"Development regressions in the stats feature; the swapped labels (line 953/954) can send a debugger to the wrong builder, so awareness of the mismatch matters.","solutions":["Update Atuin and report the panic upstream — mention that the message labels are swapped on lines 953/954","When debugging, trust the line number over the message text: database.rs:953 is the 'total' builder","Developers: fix the label mismatch while patching (`total.sql().expect(\"issue in stats total query\")`)","Add rendering unit tests for all seven stats builders"],"exampleFix":"// before (label mismatch in the codebase)\nlet total = total.sql().expect(\"issue in stats average query\");\n// after (corrected label if patching locally)\nlet total = total.sql().expect(\"issue in stats total query\");","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"let handle = std::thread::spawn(move || db.stats());\nmatch handle.join() {\n    Ok(Ok(s)) => { /* stats */ }\n    Err(_) => { /* internal panic on line 953 = 'total' builder (label is swapped); report upstream */ }\n}","preventionTips":["When debugging, map line numbers to builders, not message text (953=total, 954=average)","Fix the swapped expect labels when patching stats code locally","Unit-test rendering of the total query separately","Report upstream occurrences with backtrace line numbers"],"tags":["database","panic","invariant","stats","sqlite","sql-builder","rust","atuin"],"backgroundTag":"internal-invariant-panic","analyzedSha":"202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1","analyzedAt":"2026-08-16T19:30:24.731Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}