{"record":{"id":"0d706a38bfc7f617","repo":"multica-ai/multica","slug":"task-usage-hourly-pre-103-hook-w","errorCode":null,"errorMessage":"task_usage_hourly pre-103 hook: %w","messagePattern":"task_usage_hourly pre-103 hook: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/cmd/migrate/main.go","lineNumber":198,"sourceCode":"\t\t\treturn fmt.Errorf(\"relation %q exists but is not an index\", indexRegclass)\n\t\t}\n\t\tif isValid {\n\t\t\treturn nil\n\t\t}\n\n\t\tqualifiedName := pgx.Identifier{schemaName, relationName}.Sanitize()\n\t\tif _, err := pool.Exec(ctx, \"DROP INDEX CONCURRENTLY IF EXISTS \"+qualifiedName); err != nil {\n\t\t\treturn fmt.Errorf(\"drop invalid concurrent index %s: %w\", qualifiedName, err)\n\t\t}\n\t\tslog.Warn(\"removed invalid index before migration retry\", \"index\", qualifiedName)\n\t\treturn nil\n\t}\n}\n\nfunc runTaskUsageHourlyHook(ctx context.Context, pool *pgxpool.Pool) error {\n\tres, err := taskusagebackfill.Hook(ctx, pool, taskusagebackfill.HookOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"task_usage_hourly pre-103 hook: %w\", err)\n\t}\n\tif res.Skipped != \"\" {\n\t\tslog.Info(\"task_usage hourly rollup hook: skipped\",\n\t\t\t\"reason\", res.Skipped,\n\t\t\t\"watermark_stamped\", res.WatermarkStamped)\n\t\treturn nil\n\t}\n\tslog.Info(\"task_usage hourly rollup hook: backfill complete\",\n\t\t\"slices\", res.SlicesProcessed,\n\t\t\"rows_touched\", res.RowsTouched,\n\t\t\"from\", res.From.Format(\"2006-01-02T15:04:05Z07:00\"),\n\t\t\"to\", res.To.Format(\"2006-01-02T15:04:05Z07:00\"))\n\treturn nil\n}\n\n// runAttributionStrictHook backfills accountable_user_id from\n// originator_user_id before migration 198 validates the strict attribution\n// constraint, so self-hosted upgrades that never ran the out-of-band","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/cmd/migrate/main.go#L180-L216","documentation":"The pre-migration hook that backfills task_usage_hourly data before migration 103 (via taskusagebackfill.Hook) returned an error, aborting the migration run before schema_migrations is updated. The hook wraps the same slice/rollup machinery as the standalone backfill command, so its failure causes mirror errors 184–187: missing tables/functions, cancelled context, lock contention, or timeouts.","triggerScenarios":"task_usage exists but task_usage_hourly schema objects are missing or half-applied; a huge task_usage table making the in-migration backfill exceed statement_timeout; concurrent cron rollup contending on row locks; connection drop mid-hook.","commonSituations":"Self-hosted upgrade with months of usage data — the hook runs inline during `migrate up` and takes long enough to hit timeouts or get killed.","solutions":["Pre-warm the data with the standalone tool first: run server/cmd/backfill_task_usage_hourly (it coordinates via advisory lock 4246), then re-run migrate","Raise statement_timeout/lock_timeout for the migration session","Read the wrapped error to pinpoint the failing slice/query; re-running is safe because hook failures leave the version unrecorded"],"exampleFix":"# before\nmigrate up   # pre-103 hook times out on large dataset\n\n# after\nbackfill_task_usage_hourly -db $DSN   # backfills + stamps watermark\nmigrate up                               # hook now skips/completes fast","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"res, err := taskusagebackfill.Hook(ctx, pool, taskusagebackfill.HookOptions{})\nif err != nil {\n    return fmt.Errorf(\"task_usage_hourly pre-103 hook: %w\", err)\n}","preventionTips":["Pre-run the standalone backfill tool on large datasets before upgrading","Raise statement timeouts for `migrate up` on production","Hook failures leave the version unrecorded, so re-running is always safe"],"tags":["go","postgres","migration","backfill","hook"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}