atuinsh/atuin · error
issue in stats exits query
Error message
issue in stats exits query
What it means
Panic while compiling the exit-code histogram query. SqlBuilder::sql() returns Err only when the builder cannot render valid SQL; the guarded builder (database.rs:858-859) selects `exit, count(1) as count` from history filtered by `command = ?1` and grouped by `exit` — all hardcoded, so the failure branch is unreachable for any runtime input.
Solutions
- Restore the pinned sqlbuilder version in Cargo.lock (this is the only realistic cause)
- Guard dependency upgrades with a unit test that builds all seven stats queries and asserts .sql() returns Ok
- If it reproduces on the pinned version, capture the builder construction (fields, and_where, group_by) and file an atuin issue — the panic text does not say 'please report', but it is the same class as the list-query one
Defensive patterns
Strategy: try-catch
Try / catch
let stats = std::panic::catch_unwind(|| {
tokio::task::block_on(database.stats(&history))
});
let _ = stats.map_err(|_| tracing::error!("stats exits histogram builder panicked")); Prevention
- Freeze sqlbuilder in CI builds; breakage here only follows dependency changes
- Add compile-assert unit tests for each stats builder when upgrading
- Never assume database contents can trigger this — it cannot
When it happens
Trigger: Database::stats() with an incompatible sqlbuilder version that rejects a fields()+and_where()+group_by() chain. Triggered purely by the linked library version, never by the history database contents.
Common situations: Semver-drifted sqlbuilder after a dependency refresh; building with a forked sqlbuilder. Not reproducible on the pinned workspace versions regardless of DB state.
Related errors
- issue in stats average query
- issue in stats day of week query
- issue in stats duration over time query
- fixed-layout structure cannot fail serialization
- the vector is not empty
AI-assisted analysis of atuinsh/atuin@15fe1318f1 (2026-08-19).
Data as JSON: /api/errors/9b4540487fa76edc.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin-client/src/database.rs:889
// Intentionally format the string with 01 hardcoded. We want the average runtime for the
// _entire month_, but will later parse it as a datetime for sorting
// Sqlite has no datetime so we cannot do it there, and otherwise sorting will just be a
// string sort, which won't be correct.
let mut duration_over_time = SqlBuilder::select_from("history");
duration_over_time
.fields(&[
"strftime('01-%m-%Y', ROUND(timestamp / 1000000000), 'unixepoch') AS month_year",
"avg(duration) as duration",
])
.and_where("command = ?1")
.group_by("month_year")
.having("duration > 0");
let prev = prev.sql().expect("issue in stats previous query");
let next = next.sql().expect("issue in stats next query");
let total = total.sql().expect("issue in stats average query");
let average = average.sql().expect("issue in stats previous query");
let exits = exits.sql().expect("issue in stats exits query");
let day_of_week = day_of_week.sql().expect("issue in stats day of week query");
let duration_over_time =
duration_over_time.sql().expect("issue in stats duration over time query");
// The queries are all independent, so run them concurrently on the pool.
#[allow(clippy::type_complexity)]
let (prev, next, total, average, exits, day_of_week, duration_over_time): (
_,
_,
(i64,),
(f64,),
Vec<(i64, i64)>,
Vec<(String, i64)>,
Vec<(String, f64)>,
) = tokio::try_join!(
sqlx::query(sqlx::AssertSqlSafe(prev))
.bind(h.timestamp.unix_timestamp_nanos() as i64)
.bind(&h.session)View on GitHub (pinned to 15fe1318f1)