charmbracelet/crush · error

failed to write file: %w

Error message

failed to write file: %w

What it means

This error is returned when os.WriteFile fails to persist the new content to params.FilePath after the symbol replacement was computed and permission was granted. Common wrapped causes are read-only filesystem, permission denied, disk full, or the parent directory no longer existing.

Source

Thrown at internal/agent/tools/lsp_replace_symbol.go:161

						NewContent: newContent,
					},
				})
				if err != nil {
					return fantasy.ToolResponse{}, fmt.Errorf("permission request failed: %w", err)
				}
				if !granted {
					return NewPermissionDeniedResponse(), nil
				}
			}

			if files != nil && sessionID != "" {
				if _, err := files.CreateVersion(ctx, sessionID, params.FilePath, string(content)); err != nil {
					slog.Warn("Failed to create file version before replace", "path", params.FilePath, "error", err)
				}
			}

			if err := os.WriteFile(params.FilePath, []byte(newContent), 0o644); err != nil {
				return fantasy.ToolResponse{}, fmt.Errorf("failed to write file: %w", err)
			}

			if filetracker != nil && sessionID != "" {
				filetracker.RecordRead(ctx, sessionID, params.FilePath)
			}

			notifyLSPs(ctx, lspManager, params.FilePath)

			var summary string
			switch action {
			case "replace":
				summary = fmt.Sprintf("Replaced symbol '%s' in %s (lines %d-%d)", params.Symbol, params.FilePath, startLine+1, endLine+1)
			case "add_before":
				summary = fmt.Sprintf("Inserted before symbol '%s' in %s (before line %d)", params.Symbol, params.FilePath, startLine+1)
			case "add_after":
				summary = fmt.Sprintf("Inserted after symbol '%s' in %s (after line %d)", params.Symbol, params.FilePath, endLine+1)
			case "delete":
				summary = fmt.Sprintf("Deleted symbol '%s' from %s (lines %d-%d)", params.Symbol, params.FilePath, startLine+1, endLine+1)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check the wrapped OS error (EACCES, ENOSPC, EROFS) and address the specific cause.
  2. Verify the file and directory are writable: ls -l and touch the file as the same user.
  3. Free disk space or raise quota if ENOSPC.
  4. Restore the file if it was deleted between read and write, then re-run the tool.

Example fix

// before
if err := os.WriteFile(params.FilePath, []byte(newContent), 0o644); err != nil {
	return fantasy.ToolResponse{}, fmt.Errorf("failed to write file: %w", err)
}
// after
if err := os.WriteFile(params.FilePath, []byte(newContent), 0o644); err != nil {
	slog.Error("Failed to write file after symbol replace", "path", params.FilePath, "error", err)
	return fantasy.ToolResponse{}, fmt.Errorf("failed to write file: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if err := unix.Access(filePath, unix.W_OK); err != nil {
	return fmt.Errorf("file not writable: %s: %w", filePath, err)
}

Try / catch

if err := os.WriteFile(path, data, 0o644); err != nil {
	switch {
	case errors.Is(err, fs.ErrPermission):
		// fix permissions or choose another path
	case errors.Is(err, syscall.ENOSPC):
		// free disk space
	}
	return err
}

Prevention

When it happens

Trigger: os.WriteFile returns an error during lsp_replace_symbol: file made read-only between read and write, disk quota/full, filesystem mounted read-only (container), or the file was removed by another process.

Common situations: Editor or another process locked/changed permissions on the file; running in a read-only container FS; ENOSPC on a full disk; saving to a path outside a writable mount.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/65dd93ec6707e204. Report an issue: GitHub.