pocketbase/pocketbase · error

failed to save migration file %q: %v

Error message

failed to save migration file %q: %v

What it means

Returned by migratecmd's migrateCreateHandler (plugins/migratecmd/migratecmd.go:186) when os.WriteFile fails to persist the newly generated migration file at resultFilePath. The migrations dir was just ensured via MkdirAll, so failures are file-level: permission denied, disk full, existing read-only file with the same timestamped name, or invalid path characters on the host OS.

Source

Thrown at plugins/migratecmd/migratecmd.go:186

		var templateErr error
		if p.config.TemplateLang == TemplateLangJS {
			template, templateErr = p.jsBlankTemplate()
		} else {
			template, templateErr = p.goBlankTemplate()
		}
		if templateErr != nil {
			return "", fmt.Errorf("failed to resolve create template: %v", templateErr)
		}
	}

	// ensure that the migrations dir exist
	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
		return "", err
	}

	// save the migration file
	if err := os.WriteFile(resultFilePath, []byte(template), 0644); err != nil {
		return "", fmt.Errorf("failed to save migration file %q: %v", resultFilePath, err)
	}

	if interactive {
		fmt.Printf("Successfully created file %q\n", resultFilePath)
	}

	return filename, nil
}

func (p *plugin) migrateCollectionsHandler(args []string, interactive bool) (string, error) {
	createArgs := []string{"collections_snapshot"}
	createArgs = append(createArgs, args...)

	collections := []*core.Collection{}
	if err := p.app.CollectionQuery().OrderBy("created ASC").All(&collections); err != nil {
		return "", fmt.Errorf("failed to fetch migrations list: %v", err)
	}

View on GitHub (pinned to 5d217ddb50)

Solutions

  1. Retry after fixing ownership/permissions of the migrations dir: `chown -R $(id -u) pb_migrations && chmod -R u+rwX pb_migrations`.
  2. Free disk space or raise the container writable-layer limit.
  3. Remove or chmod the pre-existing read-only file that collides with the generated name.
  4. Run the CLI as the same user that owns the application files, never mix sudo and non-sudo invocations.

Example fix

# before: root-owned migration files from a sudo run
$ sudo ./pocketbase migrate create add_posts
$ ./pocketbase migrate create add_users  # fails to write

# after: unify ownership, then run unprivileged
$ sudo chown -R $USER pb_migrations
$ ./pocketbase migrate create add_users
Defensive patterns

Strategy: validation

Validate before calling

// Verify the target file path is writable before invoking migrate create.
func migrationPathWritable(dir, filename string) bool {
    target := filepath.Join(dir, filename)
    if info, err := os.Stat(target); err == nil && info.Mode().Perm()&0o200 == 0 {
        return false // existing read-only collision
    }
    return dirWritable(dir) // reuse the CreateTemp probe from error 228
}

Prevention

When it happens

Trigger: Executing `./pocketbase migrate create` or `migrate collections` when the process cannot write into the migrations dir; a same-named file already exists and is read-only; disk exhaustion; running as non-root against a root-owned pb_migrations.

Common situations: Developers running the CLI with sudo once (files become root-owned) and later without; CI containers with size-limited writable layers; filenames containing characters rejected by Windows (e.g. ':' from a clock-formatted name).

Related errors


AI-assisted analysis of pocketbase/pocketbase@5d217ddb50 (2026-08-15). Data as JSON: /api/errors/dc65b61b3f0f31b5. Report an issue: GitHub.