{"record":{"id":"8fef7c65cc97806a","repo":"multica-ai/multica","slug":"scan-task-usage-time-range-w","errorCode":null,"errorMessage":"scan task_usage time range: %w","messagePattern":"scan task_usage time range: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/cmd/backfill_task_usage_hourly/main.go","lineNumber":119,"sourceCode":"\t// is held on a dedicated session connection for the whole run.\n\tlockConn, err := pool.Acquire(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"acquire advisory-lock connection: %w\", err)\n\t}\n\tdefer lockConn.Release()\n\tif _, err := lockConn.Exec(ctx, `SELECT pg_advisory_lock(4246)`); err != nil {\n\t\treturn fmt.Errorf(\"acquire advisory lock 4246: %w\", err)\n\t}\n\tdefer func() {\n\t\t// Unlock on a fresh context so a cancelled ctx (SIGINT) does not\n\t\t// skip the release. Releasing the connection afterwards would end\n\t\t// the session anyway, but an explicit unlock frees it immediately.\n\t\t_, _ = lockConn.Exec(context.Background(), `SELECT pg_advisory_unlock(4246)`)\n\t}()\n\n\tvar minTS, maxTS pgtype.Timestamptz\n\tif err := pool.QueryRow(ctx, `SELECT MIN(created_at), MAX(created_at) FROM task_usage`).Scan(&minTS, &maxTS); err != nil {\n\t\treturn fmt.Errorf(\"scan task_usage time range: %w\", err)\n\t}\n\tif !minTS.Valid {\n\t\tslog.Info(\"task_usage is empty; nothing to backfill\")\n\t\tif *dryRun {\n\t\t\treturn nil\n\t\t}\n\t\treturn stampWatermark(ctx, pool)\n\t}\n\n\tfrom := monthFloor(minTS.Time.UTC())\n\tend := monthFloor(maxTS.Time.UTC()).AddDate(0, 1, 0)\n\n\tif *monthsBack > 0 {\n\t\tcutoff := monthFloor(time.Now().UTC()).AddDate(0, -(*monthsBack), 0)\n\t\t// A partial backfill still stamps the watermark at now()-5min, so\n\t\t// buckets older than the cutoff are abandoned permanently: the cron\n\t\t// worker will never look back that far. That data loss must be an\n\t\t// explicit operator decision — require --force-partial to proceed.","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/cmd/backfill_task_usage_hourly/main.go#L101-L137","documentation":"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.","triggerScenarios":"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.","commonSituations":"Fresh environment where `migrate up` was skipped; pointing -db at the wrong database; Postgres restart mid-query.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"var exists bool\npool.QueryRow(ctx, `SELECT to_regclass('task_usage') IS NOT NULL`).Scan(&exists)\nif !exists { /* run migrations first */ }","typeGuard":null,"tryCatchPattern":"if err := pool.QueryRow(ctx, `SELECT MIN(created_at), MAX(created_at) FROM task_usage`).Scan(&minTS, &maxTS); err != nil {\n    return fmt.Errorf(\"scan task_usage time range: %w\", err)\n}","preventionTips":["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"],"tags":["go","postgres","schema","sql"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}