{"record":{"id":"b661512fe657ce74","repo":"multica-ai/multica","slug":"relation-q-exists-but-is-not-an-index","errorCode":null,"errorMessage":"relation %q exists but is not an index","messagePattern":"relation %q exists but is not an index","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/cmd/migrate/main.go","lineNumber":180,"sourceCode":"func cleanupInvalidConcurrentIndexHook(indexRegclass string) preMigrationHook {\n\treturn func(ctx context.Context, pool *pgxpool.Pool) error {\n\t\tvar schemaName, relationName string\n\t\tvar isIndex, isValid bool\n\t\terr := pool.QueryRow(ctx, `\n\t\t\tSELECT n.nspname, c.relname, c.relkind = 'i', COALESCE(i.indisvalid, FALSE)\n\t\t\tFROM pg_class c\n\t\t\tJOIN pg_namespace n ON n.oid = c.relnamespace\n\t\t\tLEFT JOIN pg_index i ON i.indexrelid = c.oid\n\t\t\tWHERE c.oid = to_regclass($1)\n\t\t`, indexRegclass).Scan(&schemaName, &relationName, &isIndex, &isValid)\n\t\tif errors.Is(err, pgx.ErrNoRows) {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"inspect concurrent index %q: %w\", indexRegclass, err)\n\t\t}\n\t\tif !isIndex {\n\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)","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/cmd/migrate/main.go#L162-L198","documentation":"to_regclass resolved the configured index name to an existing relation, but pg_class.relkind says it is not an index (it is a table, view, sequence, or materialized view). The migrate tool refuses to DROP INDEX CONCURRENTLY something that is not an index, because that would either fail or, worse, be the wrong object entirely.","triggerScenarios":"A table/view/sequence shares the exact name the migration expects for its index — e.g. a leftover table created by an old or hand-run script with the index's name, or a rename collision after schema drift.","commonSituations":"Self-hosted databases that diverged from the managed schema; someone created a relation named like the index (e.g. tasks_created_at_idx used as a table name); restored dump with renamed objects.","solutions":["Inspect the object: psql -c \"SELECT c.relkind, n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE c.oid = to_regclass('<name from error>')\"","If it is a stale/wrong relation, rename or drop it deliberately (after backing up), then re-run migrate","If the name is genuinely needed by other code, fix the migration's index name or resolve the collision in the schema"],"exampleFix":"-- before: collision blocks migration\n-- relation 'public.tasks_created_at_idx' is a table\n\n-- after\nALTER TABLE public.tasks_created_at_idx RENAME TO tasks_created_at_idx_old;\n-- then re-run: migrate up","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"-- guard: is the named relation actually an index?\nSELECT c.relkind = 'i' AS is_index\nFROM pg_class c WHERE c.oid = to_regclass('public.my_idx');","tryCatchPattern":"if !isIndex {\n    return fmt.Errorf(\"relation %q exists but is not an index\", indexRegclass)\n}","preventionTips":["Keep self-hosted schemas aligned with shipped migrations; avoid manual DDL that reuses index names","Audit name collisions with pg_class before migrations on drifted databases"],"tags":["go","postgres","migration","schema-collision","index"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}