charmbracelet/crush · error

failed to read file: %w

Error message

failed to read file: %w

What it means

After the stat succeeds, loadExistingFile reads the file with os.ReadFile and wraps any read failure in this error. The file exists and is not a directory, but the bytes could not be read at the moment of reading.

Source

Thrown at internal/agent/tools/edit.go:307

	lastRead := edit.filetracker.LastReadTime(edit.ctx, sessionID, filePath)
	if lastRead.IsZero() {
		return "", "", false, fantasy.NewTextErrorResponse("you must read the file before editing it. Use the View tool first"), nil
	}

	modTime := fileInfo.ModTime().Truncate(time.Second)
	if modTime.After(lastRead) {
		return "", "", false, fantasy.NewTextErrorResponse(
			fmt.Sprintf(
				"file %s has been modified since it was last read (mod time: %s, last read: %s)",
				filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
			),
		), nil
	}

	content, err := os.ReadFile(filePath)
	if err != nil {
		return "", "", false, fantasy.ToolResponse{}, fmt.Errorf("failed to read file: %w", err)
	}

	oldContent, isCrlf = fsext.ToUnixLineEndings(string(content))
	return sessionID, oldContent, isCrlf, fantasy.ToolResponse{}, nil
}

func deleteContent(edit editContext, filePath, oldString string, replaceAll bool, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
	sessionID, oldContent, isCrlf, resp, err := loadExistingFile(edit, filePath, "session ID is required for deleting content")
	if err != nil {
		return fantasy.ToolResponse{}, err
	}
	if resp.Content != "" || resp.IsError {
		return resp, nil
	}

	newContent, whitespaceCorrected, err := findAndReplace(oldContent, oldString, "", replaceAll)
	if err != nil {
		return fantasy.NewTextErrorResponse(err.Error()), nil

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read the wrapped %w cause to identify open/read failure.
  2. Re-run the edit if the file changed concurrently (deletion race).
  3. Check read permissions/ACLs on the file itself.
  4. Point the edit at a regular file, not devices/sockets/FIFOs.

Example fix

// before: file removed between stat and read (TOCTOU)
// after: make the path stable, or handle retry
content, err := os.ReadFile(filePath)
if err != nil {
	if os.IsNotExist(err) {
		return retryWithFreshStat()
	}
	return fmt.Errorf("failed to read file: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

func readableRegularFile(path string) error {
	fi, err := os.Stat(path)
	if err != nil { return err }
	if !fi.Mode().IsRegular() {
		return errors.New("not a regular file")
	}
	return syscall.Access(path, syscall.O_RDONLY)
}

Try / catch

if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.EIO) {
	// re-stat and retry once with backoff
	time.Sleep(100 * time.Millisecond)
	retry()
}

Prevention

When it happens

Trigger: os.ReadFile failing after a successful Stat: file deleted between Stat and Read (race), EACCES on open, EIO on the underlying storage, or special files (devices, sockets, FIFOs) that error on read.

Common situations: Another process deletes/replaces the file concurrently, filesystem errors on remote mounts, permission tightened by an ACL after stat, or the path points to /dev or a socket.

Related errors


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