charmbracelet/crush · warning

error reading file: %w

Error message

error reading file: %w

What it means

OpenFile reads the target file from disk with os.ReadFile before sending a textDocument/didOpen notification. If the read fails (missing file, permission denied, I/O error), the error is wrapped as "error reading file". The client intentionally skips files that are already open, so this error fires only for files that exist in the caller's model but cannot actually be read.

Source

Thrown at internal/lsp/client.go:416

	return handlesFiletype(c.name, c.fileTypes, path)
}

// OpenFile opens a file in the LSP server.
func (c *Client) OpenFile(ctx context.Context, filepath string) error {
	if !c.HandlesFile(filepath) {
		return nil
	}

	uri := string(protocol.URIFromPath(filepath))

	if _, exists := c.openFiles.Get(uri); exists {
		return nil // Already open
	}

	// Skip files that do not exist or cannot be read
	content, err := os.ReadFile(filepath)
	if err != nil {
		return fmt.Errorf("error reading file: %w", err)
	}

	// Notify the server about the opened document
	if err = c.client.NotifyDidOpenTextDocument(ctx, uri, string(powernap.DetectLanguage(filepath)), 1, string(content)); err != nil {
		return err
	}

	c.openFiles.Set(uri, &OpenFileInfo{
		Version: 1,
		URI:     protocol.DocumentURI(uri),
	})

	return nil
}

// NotifyChange notifies the server about a file change.
func (c *Client) NotifyChange(ctx context.Context, filepath string) error {
	if c == nil {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check os.Stat(filepath) before calling OpenFile and skip nonexistent paths.
  2. Verify the path is a regular file (not a directory or symlink to nothing).
  3. Fix file read permissions on the target path.
  4. Re-resolve the file path if the workspace changed (branch switch, refactor).

Example fix

// before
client.OpenFile(ctx, cfgPath) // may not exist

// after
if _, err := os.Stat(cfgPath); err == nil {
    client.OpenFile(ctx, cfgPath)
}
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(filepath)
if err != nil || info.IsDir() {
    return nil // skip: unreadable or not a regular file
}

Type guard

func readableFile(path string) bool {
    info, err := os.Stat(path)
    return err == nil && info.Mode().IsRegular()
}

Try / catch

if err := client.OpenFile(ctx, path); err != nil {
    if strings.Contains(err.Error(), "error reading file") {
        slog.Warn("Skipping unreadable file for LSP", "path", path, "error", err)
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling OpenFile (directly or via Restart/OpenFileOnDemand/openKeyConfigFiles) with a filepath that does not exist, is a directory, lacks read permission, or was deleted between discovery and opening.

Common situations: Stale file paths after a branch switch or `git clean`; trying to open generated files that don't exist yet; opening files under a path requiring elevated permissions; racing with a build that deletes/recreates the file.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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