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

  1. Check permissions on the file path and every parent directory (ls -ld each component) and add read/search access
  2. Resolve or remove any symlink loops on the path (namei -l <path> to inspect)
  3. Confirm the filesystem/mount containing the path is healthy and mounted
  4. 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

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


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