wavetermdev/waveterm · error

failed to read file: %w

Error message

failed to read file: %w

What it means

After os.Stat succeeds, ReadAppFile reads the file contents with os.ReadFile and wraps any failure (waveappstore.go:309-312). This is rarer than the stat error — the file existed at stat time but could not be read, usually due to a permissions change, the file being deleted in between (race), or an I/O error.

Source

Thrown at pkg/waveappstore/waveappstore.go:311

	appDir, err := GetAppDir(appId)
	if err != nil {
		return nil, err
	}

	filePath, err := validateAndResolveFilePath(appDir, fileName)
	if err != nil {
		return nil, err
	}

	fileInfo, err := os.Stat(filePath)
	if err != nil {
		return nil, fmt.Errorf("failed to stat file: %w", err)
	}

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

	return &FileData{
		Contents: contents,
		ModTs:    fileInfo.ModTime().UnixMilli(),
	}, nil
}

func DeleteAppFile(appId string, fileName string) error {
	if err := ValidateAppId(appId); err != nil {
		return fmt.Errorf("invalid appId: %w", err)
	}

	appDir, err := GetAppDir(appId)
	if err != nil {
		return err
	}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Unwrap and check errors.Is(err, fs.ErrPermission) — fix file ownership/permissions with chmod/chown.
  2. Retry once; if the cause was a TOCTOU race (file deleted between stat and read), a retry will surface the cleaner not-exist condition.
  3. Check dmesg/mount health if the wrapped error is an I/O error (EIO) and the home dir is on a network filesystem.
  4. Avoid concurrent delete+read on the same app file from multiple processes.

Example fix

// before
data, err := waveappstore.ReadAppFile(appId, fileName)
if err != nil { return err }

// after
data, err := waveappstore.ReadAppFile(appId, fileName)
if err != nil {
    if errors.Is(err, fs.ErrPermission) {
        return fmt.Errorf("no read permission on %s in app %s", fileName, appId)
    }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

// ensure read permission before reading
func readable(appId, fileName string) error {
    dir, err := waveappstore.GetAppDir(appId)
    if err != nil { return err }
    f, err := os.Open(filepath.Join(dir, filepath.Clean(fileName)))
    if err != nil { return err }
    return f.Close()
}

Try / catch

var data *waveappstore.FileData
var err error
for i := 0; i < 2; i++ { // one retry covers stat/read race
    data, err = waveappstore.ReadAppFile(appId, fileName)
    if err == nil || errors.Is(err, fs.ErrNotExist) {
        break
    }
}

Prevention

When it happens

Trigger: Calling ReadAppFile when the file's read permission was revoked between Stat and ReadFile, the file was removed by another process in that window, or the underlying storage reports an I/O error (failing disk, network mount dropped).

Common situations: Another Wave instance or external tool deleting/rotating files while you read; running as a user without read permission on files created by a different uid; home directory on a flaky NFS/network share.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/911980dc3eba4ad4. Report an issue: GitHub.