plandex-ai/plandex · error

failed to write %s: %s

Error message

failed to write %s: %s

What it means

After ensuring the directory exists, ApplyFiles writes the plan content with os.WriteFile(dstPath, content, 0644). A failure here (permissions, read-only FS, disk full, target is a directory) is wrapped into this error and aborts the apply.

Source

Thrown at app/cli/lib/apply.go:689

					toRevert[dstPath] = types.ApplyReversion{Content: string(bytes), Mode: mode}
					mu.Unlock()
				}
			} else {
				mu.Lock()
				updatedFiles = append(updatedFiles, path)
				toRemoveOnRollback = append(toRemoveOnRollback, dstPath)
				mu.Unlock()
				// Create the directory if it doesn't exist
				err := os.MkdirAll(filepath.Dir(dstPath), 0755)
				if err != nil {
					errCh <- fmt.Errorf("failed to create directory %s: %s", filepath.Dir(dstPath), err.Error())
					return
				}
			}
			// Write the file
			err = os.WriteFile(dstPath, []byte(content), 0644)
			if err != nil {
				errCh <- fmt.Errorf("failed to write %s: %s", dstPath, err.Error())
				return
			}
			errCh <- nil
		}(path, content)
	}

	for path, remove := range toRemove {
		go func(path string, remove bool) {
			if !remove {
				errCh <- nil
				return
			}
			// Compute destination path
			dstPath := filepath.Join(fs.ProjectRoot, path)
			// Check if the file exists
			var exists bool
			var mode os.FileMode
			info, err := os.Stat(dstPath)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Fix write permissions/ownership on the target file or its directory (chmod/chown)
  2. Verify dstPath is not a directory; remove the conflicting directory if the plan intends a file
  3. Check disk space (df -h) and remount read-only volumes as read-write
  4. Remove the immutable attribute (chattr -i) if set

Example fix

// before
-rw-r--r-- 1 root root config.yaml   # plan overwrites it as non-root
// after
$ sudo chown $USER config.yaml
$ plandex apply
Defensive patterns

Strategy: validation

Validate before calling

// pre-check writability
if info, err := os.Stat(dstPath); err == nil {
  if info.IsDir() { return fmt.Errorf("%s is a directory", dstPath) }
  f, err := os.OpenFile(dstPath, os.O_WRONLY, 0)
  if err != nil { return fmt.Errorf("%s not writable: %w", dstPath, err) }
  f.Close()
}
if err := syscall.Access(filepath.Dir(dstPath), syscall.W_OK); err != nil {
  return fmt.Errorf("directory not writable")
}

Type guard

func isWritablePath(path string) bool {
  return syscall.Access(path, syscall.W_OK) == nil
}

Try / catch

if err := os.WriteFile(dstPath, content, 0o644); err != nil {
  if errors.Is(err, fs.ErrPermission) { /* fix perms */ }
  if errors.Is(err, fs.ErrExist) { /* path is a dir */ }
  return fmt.Errorf("write %s: %w", dstPath, err)
}

Prevention

When it happens

Trigger: os.WriteFile(dstPath, []byte(content), 0644) returns an error — write permission denied, dstPath is a directory, disk full, read-only filesystem, or immutable file attribute.

Common situations: File owned by root or another user without write permission; target path is actually a directory; container/CI filesystem mounted read-only; quota exceeded on a large generated file.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/d9b70ba28420fcab. Report an issue: GitHub.