cloudreve/cloudreve · error

failed to read state file: %w

Error message

failed to read state file: %w

What it means

loadState calls os.ReadFile on migration_state.json and wraps the resulting *fs.PathError with this message. It only surfaces when util.Exists(m.statePath) reported true a moment earlier, so typical causes are a race (the file disappeared between check and read) or a permission failure (the file exists but the current user cannot read it). A path that is actually a directory also produces this error.

Source

Thrown at application/migrator/migrator.go:168

	m.v4client = v4client
	return m, nil
}

// saveState persists migration state to file
func (m *Migrator) saveState() error {
	data, err := json.Marshal(m.state)
	if err != nil {
		return fmt.Errorf("failed to marshal state: %w", err)
	}

	return os.WriteFile(m.statePath, data, 0644)
}

// loadState reads migration state from file
func (m *Migrator) loadState() error {
	data, err := os.ReadFile(m.statePath)
	if err != nil {
		return fmt.Errorf("failed to read state file: %w", err)
	}

	return json.Unmarshal(data, m.state)
}

// updateStep updates current step and persists state
func (m *Migrator) updateStep(step int) error {
	m.state.Step = step
	return m.saveState()
}

func (m *Migrator) Migrate() error {
	// Continue from the current step
	if m.state.Step <= StepSchema {
		m.l.Info("Creating basic v4 table schema...")
		if err := m.v4client.Schema.Create(context.Background()); err != nil {
			return fmt.Errorf("failed creating schema resources: %w", err)
		}

View on GitHub (pinned to 20c95ad73f)

Solutions

  1. Check ls -l <confDir>/migration_state.json for ownership and mode; the running user needs read access (at least 0400).
  2. Fix ownership: chown the config directory and state file to the service user, or adjust the mount's UID/GID.
  3. If the file is unexpected, move it away so the migration starts fresh (clean the v4 database first).
  4. Re-run the migrator after the fix.
Defensive patterns

Strategy: validation

Validate before calling

// Prove the state file is readable by the current user before starting.
func stateReadable(path string) error {
	f, err := os.OpenFile(path, os.O_RDONLY, 0)
	if err != nil {
		return fmt.Errorf("state file not readable: %w", err)
	}
	return f.Close()
}

Type guard

func isPathPermError(err error) bool {
	var pe *fs.PathError
	return errors.As(err, &pe) && errors.Is(pe.Err, fs.ErrPermission)
}

Try / catch

data, err := os.ReadFile(m.statePath)
if err != nil {
	if isPathPermError(err) {
		// fix ownership/permissions of the config dir, then retry unchanged
	}
	return fmt.Errorf("failed to read state file: %w", err)
}

Prevention

When it happens

Trigger: Another process deletes or rotates the state file between the Exists check and the ReadFile; the file is owned by root while the migrator runs as an unprivileged user (typical Docker volume UID mismatch); migration_state.json path is occupied by a directory; SELinux/AppArmor denies the read.

Common situations: Switching the service user between runs; container restart with a different UID; cleanup/rotation scripts sweeping the config directory during a migration.

Related errors


AI-assisted analysis of cloudreve/cloudreve@20c95ad73f (2026-08-16). Data as JSON: /api/errors/1efbe857f9ebc426. Report an issue: GitHub.