atuinsh/atuin · error
issue in stats average query
Error message
issue in stats average query
What it means
Panic while compiling the 'total' stats SQL with sqlbuilder's SqlBuilder::sql(), which returns Result<String,String> when the accumulated builder state cannot render valid SQL. The guarded builder (database.rs:852-853) is `SELECT count(1) FROM history WHERE command = ?1` with hardcoded table/field names, so the Err branch is an unreachable invariant, not a data-dependent failure. Note the expect message is mislabeled: line 887 builds the total-count query even though the text says 'average'.
Solutions
- Confirm the panic is reproducible, then diff Cargo.lock for a sqlbuilder version change and restore the previously pinned version
- If a new sqlbuilder version is required, write a unit test that builds each stats query and calls .sql() so CI catches the regression before release
- Fix the swapped expect message (says 'stats average query' but guards the total query) so future reports are diagnosable
- Report the builder state that fails to the sqlbuilder upstream issue tracker, including the exact fields/and_where calls used here
Example fix
// before (crates/atuin-client/src/database.rs:887, message is wrong for this query)
let total = total.sql().expect("issue in stats average query");
// after
let total = total.sql().expect("issue in stats total query"); Defensive patterns
Strategy: try-catch
Try / catch
// stats() panics only on builder/dependency regressions; isolate if you call it in-process
let stats = std::panic::catch_unwind(|| {
tokio::task::block_on(database.stats(&history))
});
match stats {
Ok(Ok(s)) => { /* use stats */ }
Ok(Err(e)) => tracing::error!(?e, "stats query failed"),
Err(_) => tracing::error!("stats query builder panicked — check sqlbuilder version"),
} Prevention
- Keep sqlbuilder locked to the version atuin was built against; review Cargo.lock diffs before deploying
- Run a smoke `atuin stats` in CI after any dependency refresh
- Report a panic here upstream with your sqlbuilder version
When it happens
Trigger: Calling atuin-client's Database::stats() (the `atuin stats` path) after the sqlbuilder crate has been upgraded/downgraded to a version whose sql() rejects a builder shape this code produces (e.g., a field list or and_where combination it considers invalid). No user input, history row, or setting reaches this branch; the query is fully static.
Common situations: Cargo.lock regeneration that silently bumps sqlbuilder across an incompatible API/behavior change; using a vendored or patched sqlbuilder fork; building against a workspace with a version mismatch. Never fires in normal stats runs on the pinned dependency set.
Related errors
- issue in stats day of week query
- issue in stats duration over time query
- issue in stats exits 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/b99823278ad9a62c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin-client/src/database.rs:887
.group_by("day_of_week");
// 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))View on GitHub (pinned to 15fe1318f1)