gastownhall/beads · warning
failed to parse backup state: %w
Error message
failed to parse backup state: %w
What it means
loadBackupState wraps json.Unmarshal failures when .beads/backup_state.json exists but is not valid JSON for the backupState struct. The throttle state is corrupt, so the auto-backup machinery cannot determine the last backup time/commit.
Source
Thrown at cmd/bd/backup_export.go:68
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
// temp file's own contents, then renames it into place. This avoids a
// truncated/partial file at path if the process crashes mid-write.
//
// Two caveats this does NOT cover, narrowing the "crash-safe" claim ratherView on GitHub (pinned to 71377f2769)
Solutions
- Delete the corrupt state file: rm .beads/backup_state.json — the next command recreates it and worst case performs an extra backup.
- Inspect the file (cat .beads/backup_state.json) to confirm corruption before deleting; it contains only throttle timestamps/commit hashes, no issue data.
- If corruption recurs, ensure bd isn't being killed mid-write (check for OOM kills, abrupt shutdowns) — modern versions write atomically via atomicWriteFile.
- No data-loss risk: this file only governs backup throttling; your issues live in Dolt.
Example fix
// shell rm -f .beads/backup_state.json && bd sync # state is regenerated with fresh throttle timestamps
Defensive patterns
Strategy: validation
Validate before calling
const p = '.beads/backup_state.json'
data, _ := os.ReadFile(p)
if len(data) > 0 {
var probe map[string]any
if err := json.Unmarshal(data, &probe); err != nil {
os.Remove(p) // corrupt throttle state; safe to remove, no issue data
}
} Type guard
func isValidBackupState(b []byte) bool {
var s backupState
return json.Unmarshal(b, &s) == nil
} Try / catch
state, err := loadBackupState(dir)
if err != nil {
if strings.Contains(err.Error(), "parse backup state") {
_ = os.Remove(filepath.Join(dir, "backup_state.json"))
state = &backupState{} // regenerate; worst case an extra backup runs
} else {
return err
}
} Prevention
- Don't hand-edit files under .beads/
- Avoid killing bd with SIGKILL during sync; let writes finish
- Keep bd up to date — modern versions write state atomically
- Don't rsync/partial-copy .beads directories between machines
When it happens
Trigger: backup_state.json contains truncated content (interrupted write from an older non-atomic version), an empty file, invalid characters, or JSON whose types don't match backupState fields.
Common situations: Power loss or kill -9 during a legacy write that predated atomicWriteFile; manual editing of .beads files; file truncated to 0 bytes by a crash or sync tool; copying repos with partially synced .beads state.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to marshal backup state: %w
- failed to marshal issue %s: %w
- ExternalDoltConfig: must set Socket or (Host, Port)
- no backup destination configured
- failed to remove backup: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/ecb2db16288ec416.
Report an issue: GitHub.