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
- Unwrap and check errors.Is(err, fs.ErrPermission) — fix file ownership/permissions with chmod/chown.
- Retry once; if the cause was a TOCTOU race (file deleted between stat and read), a retry will surface the cleaner not-exist condition.
- Check dmesg/mount health if the wrapped error is an I/O error (EIO) and the home dir is on a network filesystem.
- 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
- Avoid deleting/rotating app files while other processes may read them.
- Keep app file permissions consistent (0644, owned by the running user).
- Watch for home dirs on unreliable network mounts.
- Distinguish permission vs race vs I/O errors with errors.Is.
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
- failed to create backup directory: %w
- failed to create socket directory: %w
- failed to create log directory: %w
- failed to open log file: %w
- failed to read directory: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/911980dc3eba4ad4.
Report an issue: GitHub.