charmbracelet/crush · error
error checking file: %w
Error message
error checking file: %w
What it means
Before writing, the tool calls os.Stat on the target path to compare existing content and skip no-op writes. If Stat fails with an error other than NotExist (permissions, I/O error, path issues), the tool aborts and wraps the OS error. The write never proceeds because the tool cannot safely determine the file's state.
Source
Thrown at internal/agent/tools/write.go:86
fileInfo, err := os.Stat(filePath)
if err == nil {
if fileInfo.IsDir() {
return fantasy.NewTextErrorResponse(fmt.Sprintf("Path is a directory, not a file: %s", filePath)), nil
}
modTime := fileInfo.ModTime().Truncate(time.Second)
lastRead := filetracker.LastReadTime(ctx, sessionID, filePath)
if modTime.After(lastRead) {
return fantasy.NewTextErrorResponse(fmt.Sprintf("File %s has been modified since it was last read.\nLast modification: %s\nLast read: %s\n\nPlease read the file again before modifying it.",
filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339))), nil
}
oldContent, readErr := os.ReadFile(filePath)
if readErr == nil && string(oldContent) == params.Content {
return fantasy.NewTextErrorResponse(fmt.Sprintf("File %s already contains the exact content. No changes made.", filePath)), nil
}
} else if !os.IsNotExist(err) {
return fantasy.ToolResponse{}, fmt.Errorf("error checking file: %w", err)
}
dir := filepath.Dir(filePath)
if err = os.MkdirAll(dir, 0o755); err != nil {
return fantasy.ToolResponse{}, fmt.Errorf("error creating directory: %w", err)
}
oldContent := ""
if fileInfo != nil && !fileInfo.IsDir() {
oldBytes, readErr := os.ReadFile(filePath)
if readErr == nil {
oldContent = string(oldBytes)
}
}
diff, additions, removals := diff.GenerateDiff(
oldContent,
params.Content,View on GitHub (pinned to 7944b8e522)
Solutions
- Check permissions on every directory component of the target path (need +x on dirs).
- Verify the path is valid: no intermediate component is a regular file (ls each segment).
- If on a network/overlay mount, test Stat from the same user the agent runs as.
- As a fallback, choose a writable path (project working dir) instead of one outside it.
Example fix
// before
err := tool.Run(ctx, fantasy.ToolCall{Input: `{"file_path":"/etc/hosts.d/app.conf","content":"..."}`})
// after
// use a path inside the working directory the process can traverse
err := tool.Run(ctx, fantasy.ToolCall{Input: `{"file_path":"config/app.conf","content":"..."}`}) Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := os.Stat(targetPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("cannot access %s: %w", targetPath, err)
} Type guard
func statAccessible(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsNotExist(err)
} Try / catch
var pathErr *fs.PathError
if errors.As(err, &pathErr) && errors.Is(err, fs.ErrPermission) {
return fmt.Errorf("permission denied checking %s; fix directory execute bits", pathErr.Path)
} Prevention
- Keep agent writes inside the project working directory.
- Check directory execute/search permissions for the running user before automating writes.
- Avoid paths on read-only or flaky mounts.
When it happens
Trigger: os.Stat(filePath) returns an error that is not os.IsNotExist — e.g. a parent directory in the path is not searchable (chmod 000), the path is on a failing mount, or an intermediate path component is a file, not a directory.
Common situations: Writing into a directory the agent user cannot traverse; ENOTDIR from a bad path segment; NFS/overlayfs glitches in containerized environments; SELinux/AppArmor denying access.
Related errors
- error creating directory: %w
- error writing file: %w
- failed to create data directory: %q %w
- failed to create .gitignore file: %q %w
- session ID is required for executing shell command
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/f5100b4b4ef8e561.
Report an issue: GitHub.