gastownhall/beads · error
failed to read backup state: %w
Error message
failed to read backup state: %w
What it means
loadBackupState in cmd/bd/backup_export.go reads .beads/backup_state.json to decide whether an auto-backup is due. This error wraps any os.ReadFile failure that is NOT a missing file (a missing file is treated as a fresh empty state). It means the state file exists but could not be read (permissions, I/O error, path is a directory).
Source
Thrown at cmd/bd/backup_export.go:64
if beadsDir == "" {
return "", fmt.Errorf("%s; %s", activeWorkspaceNotFoundError(), diagHint())
}
dir := filepath.Join(beadsDir, "backup")
if err := os.MkdirAll(dir, 0700); err != nil {
return "", fmt.Errorf("failed to create backup directory: %w", err)
}
return dir, nil
}
// loadBackupState reads the backup state file, returning a zero state if missing.
func loadBackupState(dir string) (*backupState, error) {
path := filepath.Join(dir, "backup_state.json")
data, err := os.ReadFile(path) //nolint:gosec // path is constructed internally
if os.IsNotExist(err) {
return &backupState{}, nil
}
if err != nil {
return nil, fmt.Errorf("failed to read backup state: %w", err)
}
var state backupState
if err := json.Unmarshal(data, &state); err != nil {
return nil, fmt.Errorf("failed to parse backup state: %w", err)
}
return &state, nil
}
// saveBackupState writes the backup state file atomically.
func saveBackupState(dir string, state *backupState) error {
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal backup state: %w", err)
}
return atomicWriteFile(filepath.Join(dir, "backup_state.json"), data)
}
// atomicWriteFile writes data to a same-directory temp file, fsyncs theView on GitHub (pinned to 71377f2769)
Solutions
- Check file permissions: ls -l .beads/backup_state.json and chmod/chown so the running user can read it.
- If the file is a directory or unreadable junk, remove it: rm -f .beads/backup_state.json (bd recreates it on the next backup).
- Verify the .beads directory and filesystem are writable and healthy (df, dmesg for I/O errors).
- Re-run the command; if it persists, run as the user who owns the beads workspace instead of root/sudo.
Example fix
// before (shell) sudo bd sync // after bd sync # run as the workspace owner so .beads/backup_state.json stays readable/writable by the same user
Defensive patterns
Strategy: fallback
Validate before calling
const p = '.beads/backup_state.json'
if st, err := os.Stat(p); err == nil && st.IsDir() {
os.RemoveAll(p) // directory where a file is expected
}
// ensure readable
f, err := os.OpenFile(p, os.O_RDONLY, 0)
if err == nil { f.Close() } Type guard
func isPermissionErr(err error) bool {
var pe *fs.PathError
return errors.As(err, &pe) && errors.Is(pe.Err, fs.ErrPermission)
} Try / catch
state, err := loadBackupState(dir)
if err != nil {
var pe *fs.PathError
if errors.As(err, &pe) && (errors.Is(pe.Err, fs.ErrPermission) || errors.Is(pe.Err, syscall.EIO)) {
log.Printf("backup state unreadable, starting fresh: %v", err)
state = &backupState{}
} else {
return err
}
} Prevention
- Run all bd commands as the same user that owns the workspace
- Never create files/dirs named like bd's internal state files in .beads
- Monitor filesystem health if .beads lives on network storage
When it happens
Trigger: os.ReadFile(backup_state.json) fails with an error where os.IsNotExist(err) is false: e.g. permission denied, EIO, or the path is a directory named backup_state.json.
Common situations: Running bd as a different user than the one that created the state file (root vs user); read-only or corrupted filesystem; a directory accidentally created at .beads/backup_state.json; NFS/disk errors.
Related errors
- failed to write issue %s: %w
- failed to create backup dir in git-repo: %w
- failed to create backup directory: %w
- failed to write temp file: %w
- failed to sync temp file: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e34969003cf216bc.
Report an issue: GitHub.