{"record":{"id":"4777ff1f3fdc2a3c","repo":"gastownhall/beads","slug":"fetch-failed-w","errorCode":null,"errorMessage":"fetch failed: %w","messagePattern":"fetch failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/federation.go","lineNumber":365,"sourceCode":"\t}\n\n\t// GH#2474: match PullFrom — commit pending changes before the merge,\n\t// INCLUDING config (where kv.memory.* rows live). Plain Commit excludes\n\t// config (GH#2455), so federation metadata writes such as add-peer plus any\n\t// persistent memories would otherwise leave the working set dirty and wedge\n\t// DOLT_MERGE (\"cannot merge with uncommitted changes\").\n\tif !s.readOnly {\n\t\tif err := s.commitBeforePull(ctx, \"auto-commit before sync\"); err != nil {\n\t\t\tif !isDoltNothingToCommit(err) {\n\t\t\t\tresult.Error = fmt.Errorf(\"failed to commit pending changes before sync: %w\", err)\n\t\t\t\treturn result, result.Error\n\t\t\t}\n\t\t}\n\t}\n\n\t// Step 1: Fetch from peer\n\tif err := s.Fetch(ctx, peer); err != nil {\n\t\tresult.Error = fmt.Errorf(\"fetch failed: %w\", err)\n\t\treturn result, result.Error\n\t}\n\tresult.Fetched = true\n\n\t// Step 2: Get status before merge\n\tbeforeCommit, _ := s.GetCurrentCommit(ctx) // Best effort: empty commit hash means diff won't be logged\n\n\t// Step 3: Merge peer's branch\n\tremoteBranch := fmt.Sprintf(\"%s/%s\", peer, s.branch)\n\tconflicts, err := s.Merge(ctx, remoteBranch)\n\tif err != nil {\n\t\tresult.Error = fmt.Errorf(\"merge failed: %w\", err)\n\t\treturn result, result.Error\n\t}\n\n\t// Step 4: Handle conflicts if any\n\tif len(conflicts) > 0 {\n\t\tresult.Conflicts = conflicts","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/federation.go#L347-L383","documentation":"Sync wraps any Fetch failure (see error 2522) with this prefix at Step 1 and aborts the sync. Nothing has been merged yet, so the database is unchanged; result.Fetched stays false. The wrapped cause carries the underlying fetch error.","triggerScenarios":"store.Sync(ctx, peer, strategy, opts) where s.Fetch(ctx, peer) returns non-nil — unreachable peer, unknown remote, bad credentials, CLI fetch subprocess failure, timeout.","commonSituations":"Peer server offline or DNS failure; expired credentials; remote renamed; intermittent VPN/firewall blocking the peer URL during scheduled syncs.","solutions":["Read the wrapped cause (%w) to distinguish network vs auth vs unknown-remote","Verify peer connectivity and remote config (`dolt remote -v`, `dolt ls-remote <peer>`)","Refresh credentials for the peer and retry the sync","Add retry/backoff around Sync for transient outages","If SQL-route timeouts recur, ensure the CLI fetch route is used for git-protocol remotes"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if !remoteConfigured(peer) { return fmt.Errorf(\"peer %q not configured\", peer) }\nif !networkReachable(peerURL) { return ErrPeerUnreachable }","typeGuard":null,"tryCatchPattern":"result, err := store.Sync(ctx, peer, \"\", opts)\nif err != nil && strings.HasPrefix(err.Error(), \"fetch failed:\") {\n    return retryWithBackoff(3, func() error {\n        _, err = store.Sync(ctx, peer, \"\", opts); return err\n    })\n}","preventionTips":["Health-check the peer before each scheduled sync","Use retry/backoff for transient network conditions","Keep remote names/URLs stable across re-clones; migrate rather than rename"],"tags":["dolt","federation","sync","fetch","network"],"backgroundTag":"git-fetch-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}