{"record":{"id":"9e1e01db12184d83","repo":"gastownhall/beads","slug":"failed-to-marshal-backup-state-w","errorCode":null,"errorMessage":"failed to marshal backup state: %w","messagePattern":"failed to marshal backup state: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"cmd/bd/backup_export.go","lineNumber":77,"sourceCode":"\tdata, err := os.ReadFile(path) //nolint:gosec // path is constructed internally\n\tif os.IsNotExist(err) {\n\t\treturn &backupState{}, nil\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read backup state: %w\", err)\n\t}\n\tvar state backupState\n\tif err := json.Unmarshal(data, &state); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse backup state: %w\", err)\n\t}\n\treturn &state, nil\n}\n\n// saveBackupState writes the backup state file atomically.\nfunc saveBackupState(dir string, state *backupState) error {\n\tdata, err := json.MarshalIndent(state, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal backup state: %w\", err)\n\t}\n\treturn atomicWriteFile(filepath.Join(dir, \"backup_state.json\"), data)\n}\n\n// atomicWriteFile writes data to a same-directory temp file, fsyncs the\n// temp file's own contents, then renames it into place. This avoids a\n// truncated/partial file at path if the process crashes mid-write.\n//\n// Two caveats this does NOT cover, narrowing the \"crash-safe\" claim rather\n// than the implementation (existing callers' behavior is unchanged here):\n//   - Only the temp file's contents are fsynced, not the parent directory\n//     entry; a crash between the rename and a subsequent directory fsync\n//     can still lose the rename itself on some filesystems.\n//   - os.Rename's atomic-replace guarantee is a POSIX/Unix property; it is\n//     not guaranteed on Windows. It also does not follow a symlink at\n//     path — it replaces whatever is there, symlink or not — so a caller\n//     that must preserve a symlink's target should resolve path with\n//     filepath.EvalSymlinks first (see cmd/bd/proxied_server.go).","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/backup_export.go#L59-L95","documentation":"saveBackupState wraps json.MarshalIndent failures when serializing the backupState struct before writing backup_state.json. Marshal of a plain struct of strings/times should never fail in practice; this is a defensive wrapper around an effectively impossible error path.","triggerScenarios":"json.MarshalIndent(state) returns non-nil — theoretically only if backupState contained an unsupported type (chan, func, invalid time.Time from corrupt in-memory state).","commonSituations":"Essentially never seen in production; backupState holds only string and time fields. Would indicate a code change introduced an unmarshalable field.","solutions":["Re-run the command; transient state may resolve it.","If reproducible, it indicates a bug in the backupState struct definition — check recently added fields and file an issue with the bd version.","Update bd to the latest version in case the struct regression was already fixed."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// backupState is a plain struct of strings/times; nothing to pre-validate.\n// Marshal failure implies a build/struct bug — verify bd version:\n// bd --version","typeGuard":null,"tryCatchPattern":"if err := saveBackupState(dir, state); err != nil {\n    if strings.Contains(err.Error(), \"marshal backup state\") {\n        log.Printf(\"bug: unmarshalable backupState (bd version %s): %v\", version, err)\n    }\n    return err\n}","preventionTips":["Keep bd updated to the latest release","If this fires, it is a code bug — report it with bd --version and reproduction steps"],"tags":["go","json","serialization","backup"],"backgroundTag":"json-marshal-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}