{"record":{"id":"8c8ce8617504b23a","repo":"multica-ai/multica","slug":"create-migrations-table-w","errorCode":null,"errorMessage":"create migrations table: %w","messagePattern":"create migrations table: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/cmd/migrate/main.go","lineNumber":395,"sourceCode":"\t// Best-effort explicit unlock on the success path. On error returns\n\t// the defer still runs; on os.Exit error paths in main() it does not,\n\t// but session-level advisory locks are released automatically when\n\t// the connection closes at process exit, so the next runner is never\n\t// permanently blocked.\n\tdefer func() {\n\t\tif _, err := conn.Exec(ctx, \"SELECT pg_advisory_unlock($1)\", lockKey); err != nil {\n\t\t\tslog.Warn(\"failed to release migration advisory lock\", \"error\", err)\n\t\t}\n\t}()\n\n\t// Create migrations tracking table.\n\tif _, err := conn.Exec(ctx, fmt.Sprintf(`\n\t\tCREATE TABLE IF NOT EXISTS %s (\n\t\t\tversion TEXT PRIMARY KEY,\n\t\t\tapplied_at TIMESTAMPTZ NOT NULL DEFAULT now()\n\t\t)\n\t`, tableIdent)); err != nil {\n\t\treturn fmt.Errorf(\"create migrations table: %w\", err)\n\t}\n\n\texistsSQL := fmt.Sprintf(\"SELECT EXISTS(SELECT 1 FROM %s WHERE version = $1)\", tableIdent)\n\tinsertSQL := fmt.Sprintf(\"INSERT INTO %s (version) VALUES ($1)\", tableIdent)\n\tdeleteSQL := fmt.Sprintf(\"DELETE FROM %s WHERE version = $1\", tableIdent)\n\n\tfor _, file := range opts.Files {\n\t\tversion := migrations.ExtractVersion(file)\n\n\t\tvar exists bool\n\t\tif err := conn.QueryRow(ctx, existsSQL, version).Scan(&exists); err != nil {\n\t\t\treturn fmt.Errorf(\"check migration %q: %w\", version, err)\n\t\t}\n\n\t\tif opts.Direction == \"up\" {\n\t\t\tif exists {\n\t\t\t\tfmt.Printf(\"  skip  %s (already applied)\\n\", version)\n\t\t\t\tcontinue","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/cmd/migrate/main.go#L377-L413","documentation":"CREATE TABLE IF NOT EXISTS for the schema_migrations tracking table failed. IF EXISTS-style idempotence only guards against the table already existing properly; errors arise from insufficient CREATE privilege on the schema, an invalid table identifier at the SQL level (after quoting), disk/filesystem issues, or a cancelled context.","triggerScenarios":"Running the migration role without CREATE on the target schema; the custom -schema-migrations-table targets a schema that does not exist; ctx cancelled right after lock acquisition.","commonSituations":"Least-privilege DB users missing DDL grants; pointing at a schema-qualified tracking table whose schema was never created.","solutions":["Grant the migration role CREATE on the schema (or use the schema owner)","Create the target schema first if using schema-qualified tracking table names","Re-run after fixing privileges; the run is resumable"],"exampleFix":"-- before: migrate role lacks CREATE\nGRANT USAGE ON SCHEMA public TO migrate_user;  -- insufficient\n\n-- after\nGRANT USAGE, CREATE ON SCHEMA public TO migrate_user;","handlingStrategy":"validation","validationCode":"-- verify the role can create tables before running migrations\nSELECT has_schema_privilege(current_user, 'public', 'CREATE');","typeGuard":null,"tryCatchPattern":"if _, err := conn.Exec(ctx, createTableSQL); err != nil {\n    return fmt.Errorf(\"create migrations table: %w\", err)\n}","preventionTips":["Run migrations as the schema owner or a role with CREATE","Create custom target schemas before referencing them in the tracking-table name","Re-run after fixing privileges; IF NOT EXISTS keeps it idempotent"],"tags":["go","postgres","migration","ddl","permissions"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}