multica-ai/multica · error
scan task_usage time range: %w
Error message
scan task_usage time range: %w
What it means
Scanning SELECT MIN(created_at), MAX(created_at) FROM task_usage failed. Failure modes: the task_usage table does not exist (schema not migrated — error 42P01), a column type mismatch when scanning into pgtype.Timestamptz, the query was cancelled, or the connection dropped. Note MIN/MAX return NULL on an empty table, which pgtype.Timestamptz scans as Valid=false — that is handled, not an error.
Source
Thrown at server/cmd/backfill_task_usage_hourly/main.go:119
// is held on a dedicated session connection for the whole run.
lockConn, err := pool.Acquire(ctx)
if err != nil {
return fmt.Errorf("acquire advisory-lock connection: %w", err)
}
defer lockConn.Release()
if _, err := lockConn.Exec(ctx, `SELECT pg_advisory_lock(4246)`); err != nil {
return fmt.Errorf("acquire advisory lock 4246: %w", err)
}
defer func() {
// Unlock on a fresh context so a cancelled ctx (SIGINT) does not
// skip the release. Releasing the connection afterwards would end
// the session anyway, but an explicit unlock frees it immediately.
_, _ = lockConn.Exec(context.Background(), `SELECT pg_advisory_unlock(4246)`)
}()
var minTS, maxTS pgtype.Timestamptz
if err := pool.QueryRow(ctx, `SELECT MIN(created_at), MAX(created_at) FROM task_usage`).Scan(&minTS, &maxTS); err != nil {
return fmt.Errorf("scan task_usage time range: %w", err)
}
if !minTS.Valid {
slog.Info("task_usage is empty; nothing to backfill")
if *dryRun {
return nil
}
return stampWatermark(ctx, pool)
}
from := monthFloor(minTS.Time.UTC())
end := monthFloor(maxTS.Time.UTC()).AddDate(0, 1, 0)
if *monthsBack > 0 {
cutoff := monthFloor(time.Now().UTC()).AddDate(0, -(*monthsBack), 0)
// A partial backfill still stamps the watermark at now()-5min, so
// buckets older than the cutoff are abandoned permanently: the cron
// worker will never look back that far. That data loss must be an
// explicit operator decision — require --force-partial to proceed.View on GitHub (pinned to 2c0912b6ec)
Solutions
- Run the migration tool first (server/cmd/migrate) so task_usage exists
- Confirm the DSN targets the right database (\dt in psql shows task_usage)
- Check created_at's column type matches timestamptz expectations
Defensive patterns
Strategy: validation
Validate before calling
var exists bool
pool.QueryRow(ctx, `SELECT to_regclass('task_usage') IS NOT NULL`).Scan(&exists)
if !exists { /* run migrations first */ } Try / catch
if err := pool.QueryRow(ctx, `SELECT MIN(created_at), MAX(created_at) FROM task_usage`).Scan(&minTS, &maxTS); err != nil {
return fmt.Errorf("scan task_usage time range: %w", err)
} Prevention
- Always run server/cmd/migrate before the backfill command
- Smoke-test the DSN in psql before long backfill runs
- Keep created_at as timestamptz in any schema changes
When it happens
Trigger: Running the backfill against a database where migrations have not created task_usage; scanning a created_at column whose type is not timestamptz (e.g. migrated to a different type); ctx cancelled mid-query.
Common situations: Fresh environment where `migrate up` was skipped; pointing -db at the wrong database; Postgres restart mid-query.
Related errors
- stamp watermark: %w
- check migration %q: %w
- apply migration %q: %w
- identifier %q has more than one dot; only schema.table is su
- connect to database: %w
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/8fef7c65cc97806a.
Report an issue: GitHub.