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
- Check os.Stat(filepath) before calling OpenFile and skip nonexistent paths.
- Verify the path is a regular file (not a directory or symlink to nothing).
- Fix file read permissions on the target path.
- 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
- Only open files you just listed from the filesystem, not cached paths.
- Skip files under paths known to be generated or ephemeral.
- Run crush with sufficient permissions for the workspace it indexes.
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
- failed to read file: %w
- failed to write file: %w
- failed to create file: %w
- failed to delete directory recursively: %w
- failed to delete file: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/e8737507648aa3a0.
Report an issue: GitHub.