siyuan-note/siyuan · error

stat destination [%s] failed: %w

Error message

stat destination [%s] failed: %w

What it means

While pre-checking moves, os.Lstat on a destination path inside <base>/siyuan returned an error other than fs.ErrNotExist (i.e. not a clean 'does not exist' answer). Migration aborts because the existence check itself failed, so it cannot guarantee a safe rename.

Source

Thrown at kernel/util/working_mobile.go:188

	if 0 < len(destinationEntries) {
		return false, fmt.Errorf("destination [%s] is not empty", defaultWorkspaceDir)
	}

	var moves []workspaceDirMove
	for _, name := range legacyIOSWorkspaceEntries {
		from := filepath.Join(workspaceBaseDir, name)
		if _, statErr := os.Lstat(from); statErr != nil {
			if errors.Is(statErr, fs.ErrNotExist) {
				continue
			}
			return false, fmt.Errorf("stat source [%s] failed: %w", from, statErr)
		}

		to := filepath.Join(defaultWorkspaceDir, name)
		if _, statErr := os.Lstat(to); statErr == nil {
			return false, fmt.Errorf("destination [%s] already exists", to)
		} else if !errors.Is(statErr, fs.ErrNotExist) {
			return false, fmt.Errorf("stat destination [%s] failed: %w", to, statErr)
		}
		moves = append(moves, workspaceDirMove{from: from, to: to})
	}

	var completed []workspaceDirMove
	for _, move := range moves {
		if renameErr := os.Rename(move.from, move.to); renameErr != nil {
			var rollbackErrors []error
			for i := len(completed) - 1; 0 <= i; i-- {
				completedMove := completed[i]
				if rollbackErr := os.Rename(completedMove.to, completedMove.from); rollbackErr != nil {
					rollbackErrors = append(rollbackErrors, fmt.Errorf("rollback [%s] to [%s] failed: %w",
						completedMove.to, completedMove.from, rollbackErr))
				}
			}
			return false, errors.Join(append([]error{fmt.Errorf("move [%s] to [%s] failed: %w", move.from, move.to, renameErr)}, rollbackErrors...)...)
		}
		completed = append(completed, move)

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Inspect the wrapped %w error to distinguish permission vs I/O vs transient causes
  2. Ensure no other SiYuan instance or sync process is writing to the workspace during migration; restart and retry
  3. Fix destination directory permissions so Lstat can complete
  4. If persistent, verify device storage health / reinstall the app to recreate a clean container
Defensive patterns

Strategy: retry

Validate before calling

// Go: confirm destination directory is stat-able and stable before migrating
dest := filepath.Join(base, "siyuan")
if fi, err := os.Stat(dest); err != nil || !fi.IsDir() {
    return fmt.Errorf("destination %s not a usable directory", dest)
}

Try / catch

// Go
for attempt := 0; attempt < 3; attempt++ {
    migrated, err := migrateLegacyIOSWorkspace(base)
    if err == nil { break }
    if !strings.Contains(err.Error(), "stat destination") { return err }
    time.Sleep(200 * time.Millisecond) // transient concurrent-access failure
}

Prevention

When it happens

Trigger: migrateLegacyIOSWorkspace's destination Lstat hits EACCES/EIO or similar on <base>/siyuan/<name> after the destination ReadDir already succeeded.

Common situations: Race with another process creating/removing files in the destination concurrently; transient I/O errors; permission changes mid-run; filesystem corruption on the device.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/23f4b3f2c78234c0. Report an issue: GitHub.