multica-ai/multica · error
read migration %q: %w
Error message
read migration %q: %w
What it means
os.ReadFile failed for a migration SQL file listed in opts.Files. The runner needs the file's bytes to exec it; failure means the path does not exist, is a directory, or the process lacks read permission. The path is exactly what was passed in the file list, so this usually indicates the files were moved/deleted between listing and reading, or the list was built against a different root.
Source
Thrown at server/cmd/migrate/main.go:424
if err := conn.QueryRow(ctx, existsSQL, version).Scan(&exists); err != nil {
return fmt.Errorf("check migration %q: %w", version, err)
}
if opts.Direction == "up" {
if exists {
fmt.Printf(" skip %s (already applied)\n", version)
continue
}
} else {
if !exists {
fmt.Printf(" skip %s (not applied)\n", version)
continue
}
}
sql, err := os.ReadFile(file)
if err != nil {
return fmt.Errorf("read migration %q: %w", file, err)
}
// Run any pre-migration hook before the SQL file. Hooks
// receive the *pgxpool.Pool (not the loop's pinned conn), so
// they can acquire other session-level locks without
// colliding with migrationAdvisoryLockKey. Hook failures
// abort the run before schema_migrations is updated, so the
// same version retries cleanly on the next invocation.
if hook, ok := opts.Hooks[version]; ok && hook != nil {
slog.Info("running pre-migration hook", "version", version, "direction", opts.Direction)
if err := hook(ctx, pool); err != nil {
return fmt.Errorf("pre-migration hook for %q (%s): %w", version, opts.Direction, err)
}
}
if _, err := conn.Exec(ctx, string(sql)); err != nil {
return fmt.Errorf("apply migration %q: %w", file, err)
}View on GitHub (pinned to 2c0912b6ec)
Solutions
- Verify the exact path from the error exists at runtime: ls -l <path>
- Use absolute paths or embed the migrations via go:embed so the binary is self-contained
- If containerized, confirm the migrations dir is COPYed into the image and readable by the runtime user
Example fix
# before migrate -files=./migrations/*.sql # relative to cwd, wrong cwd at runtime # after migrate -files=/app/migrations/*.sql # or embed in Go: //go:embed migrations/*.sql var migrationFS embed.FS
Defensive patterns
Strategy: validation
Validate before calling
for _, f := range files {
if _, err := os.Stat(f); err != nil {
log.Fatalf("migration file missing: %s", f)
}
} Try / catch
sql, err := os.ReadFile(file)
if err != nil {
return fmt.Errorf("read migration %q: %w", file, err)
} Prevention
- Use absolute paths or go:embed for migration files so cwd cannot break runs
- Verify the migrations directory exists in container images (COPY + readable)
- Fail fast on stat-checks over the file list before starting the DB work
When it happens
Trigger: Embedded-vs-disk mismatch: opts.Files contains absolute/relative paths from a build step but the binary runs where those paths do not exist; file deleted between discovery and read; permission changes (e.g. container user differs from file owner).
Common situations: Running the migrate binary from a different working directory with relative file globs; CI cache stale; container image built without copying the migrations directory.
Related errors
- Invalid desktop runtime config: expected a JSON object
- Unsupported desktop runtime config schemaVersion: expected 1
- Invalid desktop runtime config: ${field} must be a non-empty
- Invalid desktop runtime config: ${field} must be a valid URL
- mint PAT: target API URL not set
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/155e6e7f573041d1.
Report an issue: GitHub.