kopia/kopia · error

placeholderPath dir creation

Error message

placeholderPath dir creation

What it means

When building a shallow placeholder path for a snapshot.EntryTypeDirectory, placeholderPath must os.MkdirAll the '<path>.kopia-entry' directory (using SafeLongFilename). If that mkdir fails (permissions, disk full, path too long), the error is wrapped as "placeholderPath dir creation".

Solutions

  1. Check the destination parent is writable by the process user and not mounted read-only
  2. Delete any stale regular file occupying the '<path>.kopia-entry' name
  3. Inspect errors.Cause(err) for EACCES/ENOSPC/ENAMETOOLONG and address it (free space, enable long paths)
  4. Re-run the operation; MkdirAll is idempotent once the obstacle is removed

Example fix

// before
mp, err := localfs.WriteShallowPlaceholder(destPath, de) // fails: dest on read-only fs
// after
if test -w parent; then ... fi — in Go:
if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
	return fmt.Errorf("destination %q not writable: %w", destPath, err)
}
mp, err := localfs.WriteShallowPlaceholder(destPath, de)
Defensive patterns

Strategy: validation

Validate before calling

parent := filepath.Dir(path)
if err := unix.Access(parent, unix.W_OK); err != nil {
	return fmt.Errorf("destination %q not writable: %w", parent, err)
}
if fi, err := os.Lstat(path + ".kopia-entry"); err == nil && !fi.IsDir() {
	return fmt.Errorf("%q exists and is not a directory", path+".kopia-entry")
}

Try / catch

mp, err := localfs.WriteShallowPlaceholder(path, de)
if err != nil {
	if os.IsPermission(errors.Cause(err)) {
		return fmt.Errorf("cannot create placeholder dir for %q: permission denied", path)
	}
	return fmt.Errorf("placeholder %q: %w", path, err)
}

Prevention

When it happens

Trigger: Calling WriteShallowPlaceholder (via writeShallowEntry) for a directory entry whose placeholder directory cannot be created: read-only filesystem, missing write permission on the parent, ENAMETOOLONG/ENOSPC, or the target path exists as a plain file blocking MkdirAll.

Common situations: Restoring/duplicating shallow placeholders onto a read-only volume or a Windows ACL-protected folder; long paths in deep trees on Windows without long-path support; an existing regular file named '<dir>.kopia-entry' left by a prior failed run.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/0669ac109507accf. Report an issue: GitHub.

Appendix: source

Thrown at fs/localfs/shallow_fs.go:31

	"github.com/kopia/kopia/internal/atomicfile"
	"github.com/kopia/kopia/internal/ospath"
	"github.com/kopia/kopia/snapshot"
)

// Helpers to implement storing of "shallow" placeholders for files or
// directory trees in a restore image. A placeholder for directory d1 is
// d1.kopia-entry/.kopia-entry. As a result, d1.kopia-entry will stat as
// a directory and show up nicely in colorized ls output. A placeholder
// for a file f1 is f1.kopia-entry.

func placeholderPath(path string, et snapshot.EntryType) (string, error) {
	switch et {
	case snapshot.EntryTypeFile:
		return path + ShallowEntrySuffix, nil
	case snapshot.EntryTypeDirectory: // Directories and regular files
		dirpath := path + ShallowEntrySuffix
		if err := os.MkdirAll(ospath.SafeLongFilename(dirpath), os.FileMode(dirMode)); err != nil {
			return "", errors.Wrap(err, "placeholderPath dir creation")
		}

		return filepath.Join(dirpath, ShallowEntrySuffix), nil
	default:
		// Shouldn't be used on links or other file types.
		return "", errors.Errorf("unsupported entry type: %v", et)
	}
}

// WriteShallowPlaceholder writes sufficient metadata into the placeholder
// file associated with path so that it can be roundtripped through
// snapshot/restore without needing to be realized in the local
// filesystem.
// TODO(rjk): Should the placeholder use the complete fs.Entry?
func WriteShallowPlaceholder(path string, de *snapshot.DirEntry) (string, error) {
	buffy := &bytes.Buffer{}
	encoder := json.NewEncoder(buffy)

View on GitHub (pinned to 82495e54b5)