{"record":{"id":"017f16b97c0a2081","repo":"gastownhall/beads","slug":"failed-to-fetch-from-peer-s-w","errorCode":null,"errorMessage":"failed to fetch from peer %s: %w","messagePattern":"failed to fetch from peer (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/federation.go","lineNumber":190,"sourceCode":"// For git-protocol remotes, uses CLI `dolt fetch` to avoid MySQL connection timeouts.\nfunc (s *DoltStore) Fetch(ctx context.Context, peer string) error {\n\tif useCLI, err := s.prepareCLIRouteForPeerGitProtocol(ctx, peer); err != nil {\n\t\treturn err\n\t} else if useCLI {\n\t\treturn s.withPeerCredentials(ctx, peer, func(creds *remoteCredentials) error {\n\t\t\treturn s.doltCLIFetchFromPeer(ctx, peer, creds)\n\t\t})\n\t}\n\treturn s.withPeerCredentials(ctx, peer, func(creds *remoteCredentials) error {\n\t\t// Credential CLI routing: route fetch through CLI subprocess.\n\t\tif useCLI, err := s.prepareCLIRouteForPeerCredentials(ctx, peer, creds); err != nil {\n\t\t\treturn err\n\t\t} else if useCLI {\n\t\t\treturn s.doltCLIFetchFromPeer(ctx, peer, creds)\n\t\t}\n\t\treturn withEnvCredentials(creds, func() error {\n\t\t\tif err := s.execWithLongTimeout(ctx, \"CALL DOLT_FETCH(?)\", peer); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to fetch from peer %s: %w\", peer, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t})\n}\n\n// ListRemotes returns configured remote names and URLs.\nfunc (s *DoltStore) ListRemotes(ctx context.Context) ([]storage.RemoteInfo, error) {\n\treturn versioncontrolops.ListRemotes(ctx, s.db)\n}\n\n// hasPersistedCLIRemote reports whether a Dolt remote is persisted on disk in\n// .dolt/repo_state.json — in the database CLI directory (CLIDir) or the dolt\n// server root (Path, per GH#2118). A freshly (auto-)started sql-server can\n// report an empty dolt_remotes table at store open even though remotes are\n// persisted on disk. The #4259 remote-migrate gate therefore consults this\n// directly so a cold-start open cannot miss the remote and migrate the shared\n// database in place.","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/federation.go#L172-L208","documentation":"Wraps a failure of `CALL DOLT_FETCH(?)` (or the CLI fetch path) when fetching refs from a peer remote without merging. Fetch is the first step of every sync, so this error usually blocks synchronization entirely. The wrapped cause distinguishes network failures, unknown remotes, and auth problems.","triggerScenarios":"Calling Fetch(ctx, peer) or Sync's Step 1 where DOLT_FETCH against the peer's remote errors — unreachable URL, TLS failure, unknown remote name, invalid credentials, or CLI fetch subprocess failure.","commonSituations":"Offline or firewalled environment; peer git server (GitHub/Forgejo) down or rate-limiting; remote name changed after re-cloning; expired token in stored credentials; MySQL connection timeout on the SQL route (why the CLI route exists).","solutions":["Check network reachability of the peer URL (`dolt ls-remote <remote>` or curl the URL)","Verify the remote name/URL with `dolt remote -v` and re-add if renamed: `dolt remote set-url <name> <url>`","Refresh stored credentials for the peer; confirm env credentials via withEnvCredentials (GITHUB_TOKEN etc.)","If the SQL route times out, force the CLI route (the code prefers it for git-protocol remotes) or increase the long-timeout window","Retry after transient outage — fetch is safe to re-run"],"exampleFix":"// before\nif err := store.Fetch(ctx, \"origin\"); err != nil { panic(err) }\n// after: retry transient fetch failures\nerr := store.Fetch(ctx, \"origin\")\nfor i := 0; err != nil && i < 3; i++ {\n    time.Sleep(time.Duration(1<<i) * time.Second)\n    err = store.Fetch(ctx, \"origin\")\n}","handlingStrategy":"retry","validationCode":"remotes, _ := store.ListRemotes(ctx)\nif !remoteExists(remotes, peer) { return fmt.Errorf(\"unknown peer %q\", peer) }\n// plus network probe:\nif err := probeRemoteURL(peerURL); err != nil { return err }","typeGuard":"func isFetchable(peer string) bool { return peer != \"\" && remoteConfigured(peer) }","tryCatchPattern":"if err := store.Fetch(ctx, peer); err != nil {\n    if isTransient(err) { // network/timeout\n        return retryWithBackoff(3, func() error { return store.Fetch(ctx, peer) })\n    }\n    return fmt.Errorf(\"fetch %s: %w\", peer, err)\n}","preventionTips":["Probe peer reachability before scheduled syncs","Rotate credentials before expiry; store them via the library's credential path","Prefer the CLI fetch route for git-protocol remotes to avoid MySQL timeouts","Alert on repeated fetch failures at the same peer URL"],"tags":["dolt","federation","fetch","network"],"backgroundTag":"git-fetch-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}