charmbracelet/crush · error
failed to access file: %w
Error message
failed to access file: %w
What it means
In the file-creation path of multi_edit, the code stats the target path before writing. If os.Stat returns an error that is neither success nor ErrNotExist — permission denied on a parent directory, I/O error, symlink loop, path is unsearchable — the error is wrapped as `failed to access file: ...` and the whole operation fails before any writes.
Source
Thrown at internal/agent/tools/multiedit.go:159
}
whitespaceCorrected = whitespaceCorrected || corrected
currentContent = newContent
}
return currentContent, failedEdits, whitespaceCorrected
}
func processMultiEditWithCreation(edit editContext, params MultiEditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
// First edit creates the file
firstEdit := params.Edits[0]
if firstEdit.OldString != "" {
return fantasy.NewTextErrorResponse("first edit must have empty old_string for file creation"), nil
}
// Check if file already exists
if _, err := os.Stat(params.FilePath); err == nil {
return fantasy.NewTextErrorResponse(fmt.Sprintf("file already exists: %s", params.FilePath)), nil
} else if !os.IsNotExist(err) {
return fantasy.ToolResponse{}, fmt.Errorf("failed to access file: %w", err)
}
// Create parent directories
dir := filepath.Dir(params.FilePath)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fantasy.ToolResponse{}, fmt.Errorf("failed to create parent directories: %w", err)
}
currentContent, failedEdits, whitespaceCorrected := applyEditsToContent(firstEdit.NewString, params.Edits[1:], 1)
// Get session and message IDs
sessionID := GetSessionFromContext(edit.ctx)
if sessionID == "" {
return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
}
// Check permissions
_, additions, removals := diff.GenerateDiff("", currentContent, strings.TrimPrefix(params.FilePath, edit.workingDir))View on GitHub (pinned to 7944b8e522)
Solutions
- Check permissions on the file path and every parent directory (ls -ld each component) and add read/search access
- Resolve or remove any symlink loops on the path (namei -l <path> to inspect)
- Confirm the filesystem/mount containing the path is healthy and mounted
- If under a sandbox/CI, grant the runner access to the target directory or choose a writable path
Example fix
// before: EACCES filePath := "/root/project/newfile.go" // parent /root not searchable // after filePath := filepath.Join(workDir, "project", "newfile.go") os.Chmod(filepath.Dir(filePath), 0o755)
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight accessibility of the target path
if _, err := os.Stat(targetPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("cannot create %s: %v", targetPath, err)
} Try / catch
resp, err := multiEditCreate(ctx, params)
if err != nil && strings.HasPrefix(err.Error(), "failed to access file") {
// fall back to a writable location under the working dir
params.FilePath = filepath.Join(workDir, filepath.Base(params.FilePath))
resp, err = multiEditCreate(ctx, params)
} Prevention
- Create files only under the project working directory, not system paths
- Verify parent-directory permissions (need x/search) before creation attempts
- Avoid writing through symlinks whose targets may be inaccessible
When it happens
Trigger: os.Stat(params.FilePath) fails with something other than nil or fs.ErrNotExist: EACCES on a parent directory, ELOOP from a symlink cycle, device errors, or a path component that is not a directory in a way Stat reports as another errno.
Common situations: Read-only or no-execute permission on a parent dir under a restrictive CI runner; broken/mutating symlink; sandboxed environment blocking stat on the path; path inside a container mount that vanished.
Related errors
- failed to access file: %w
- failed to create parent directories: %w
- failed to create parent directories: %w
- failed to create output file: %w
- session ID is required for creating a new file
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/e87970f380b5596b.
Report an issue: GitHub.