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 the

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check file permissions: ls -l .beads/backup_state.json and chmod/chown so the running user can read it.
  2. If the file is a directory or unreadable junk, remove it: rm -f .beads/backup_state.json (bd recreates it on the next backup).
  3. Verify the .beads directory and filesystem are writable and healthy (df, dmesg for I/O errors).
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/e34969003cf216bc. Report an issue: GitHub.