multica-ai/multica · error

task_usage_hourly pre-103 hook: %w

Error message

task_usage_hourly pre-103 hook: %w

What it means

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.

Source

Thrown at server/cmd/migrate/main.go:198

			return fmt.Errorf("relation %q exists but is not an index", indexRegclass)
		}
		if isValid {
			return nil
		}

		qualifiedName := pgx.Identifier{schemaName, relationName}.Sanitize()
		if _, err := pool.Exec(ctx, "DROP INDEX CONCURRENTLY IF EXISTS "+qualifiedName); err != nil {
			return fmt.Errorf("drop invalid concurrent index %s: %w", qualifiedName, err)
		}
		slog.Warn("removed invalid index before migration retry", "index", qualifiedName)
		return nil
	}
}

func runTaskUsageHourlyHook(ctx context.Context, pool *pgxpool.Pool) error {
	res, err := taskusagebackfill.Hook(ctx, pool, taskusagebackfill.HookOptions{})
	if err != nil {
		return fmt.Errorf("task_usage_hourly pre-103 hook: %w", err)
	}
	if res.Skipped != "" {
		slog.Info("task_usage hourly rollup hook: skipped",
			"reason", res.Skipped,
			"watermark_stamped", res.WatermarkStamped)
		return nil
	}
	slog.Info("task_usage hourly rollup hook: backfill complete",
		"slices", res.SlicesProcessed,
		"rows_touched", res.RowsTouched,
		"from", res.From.Format("2006-01-02T15:04:05Z07:00"),
		"to", res.To.Format("2006-01-02T15:04:05Z07:00"))
	return nil
}

// runAttributionStrictHook backfills accountable_user_id from
// originator_user_id before migration 198 validates the strict attribution
// constraint, so self-hosted upgrades that never ran the out-of-band

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. 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
  2. Raise statement_timeout/lock_timeout for the migration session
  3. Read the wrapped error to pinpoint the failing slice/query; re-running is safe because hook failures leave the version unrecorded

Example fix

# before
migrate up   # pre-103 hook times out on large dataset

# after
backfill_task_usage_hourly -db $DSN   # backfills + stamps watermark
migrate up                               # hook now skips/completes fast
Defensive patterns

Strategy: retry

Try / catch

res, err := taskusagebackfill.Hook(ctx, pool, taskusagebackfill.HookOptions{})
if err != nil {
    return fmt.Errorf("task_usage_hourly pre-103 hook: %w", err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/0d706a38bfc7f617. Report an issue: GitHub.