{"record":{"id":"82997fb53b01fcae","repo":"gastownhall/beads","slug":"w-w","errorCode":null,"errorMessage":"%w: %w","messagePattern":"%w: %w","errorType":"exception","errorClass":"versioncontrolops.ErrPullBehindFastForwardable","httpStatus":null,"severity":"warning","filePath":"internal/storage/dolt/store.go","lineNumber":4194,"sourceCode":"\t// retryable class, because its common ancestor is by definition neither tip.\n\t// Distinguishing the benign race from a genuinely failed transport would\n\t// still need the remote tip as of the transport's own fetch, which git-backed\n\t// remotes never write into this database; the merge base is the best post-hoc\n\t// split available. Classified before the display fallback below rewrites an\n\t// empty localHash.\n\tbehindFastForwardable := localHash != \"\" && mergeBase.String == localHash\n\n\tif localHash == \"\" {\n\t\tlocalHash = \"unknown\"\n\t}\n\tmergedNothing := fmt.Errorf(\"pull from %s/%s reported success but merged nothing into %s: %s is at %s while %s is at %s \"+\n\t\t\"(their common ancestor is %s), so the commits on the remote-tracking ref are not on the branch this \"+\n\t\t\"database reads. Most often another client pushed after this pull fetched and a re-run will merge it; \"+\n\t\t\"if the divergence survives repeated re-runs, the transport is not landing merges on this branch \"+\n\t\t\"(for example the dolt CLI directory and the sql-server are serving different databases or branches)\",\n\t\tremote, s.branch, s.branch, s.branch, localHash, trackingRef, remoteHash, mergeBase.String)\n\tif behindFastForwardable {\n\t\treturn fmt.Errorf(\"%w: %w\", versioncontrolops.ErrPullBehindFastForwardable, mergedNothing)\n\t}\n\treturn mergedNothing\n}\n\n// refreshTrackingRef fetches remote/s.branch into this database's\n// remote-tracking refs over a dedicated long-timeout, credential-aware\n// connection, so verifyPullLanded's comparison reads a tracking ref this\n// database just wrote. It mirrors pullTransport's own network calls: a\n// long-timeout connection (openLongTimeoutConn) wrapped in the remote's\n// credential/S3 environment (withRemoteOperationEnv), which is what lets the\n// refresh reach CLI-routed (git-protocol, credential, cloud-auth) remotes that\n// the default s.db pool — short read timeout, no CLI credentials — cannot.\n//\n// Only the FETCH runs here, and DOLT_FETCH is branch-global: it advances\n// remote-tracking refs and never touches the working branch, so the fresh\n// connection's default-branch checkout — the be-b0am hazard that makes a merge\n// on such a connection unsafe — does not apply. The containment reads in\n// verifyPullLanded stay on s.db, where the short pool timeout is right: they are","sourceCodeStart":4176,"sourceCodeEnd":4212,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/store.go#L4176-L4212","documentation":"This is the benign, retryable classification of the merged-nothing error: the local branch is a strict ancestor of the refreshed remote-tracking ref, so the remote simply moved ahead after the fetch. It wraps the detailed mergedNothing error with the sentinel versioncontrolops.ErrPullBehindFastForwardable, which bd sync treats as a push-race and retries rather than failing the tick.","triggerScenarios":"verifyPullLanded finds localHash != \"\", merge base == localHash, and remoteHash ahead — i.e., a peer client pushed new commits between this pull's fetch and its verification. Only reachable via Pull()/PullRemote() while another writer pushes to the same remote branch.","commonSituations":"Multiple machines/agents syncing the same beads database concurrently; CI running bd sync while a developer works; scheduled federation peers pulling in overlapping windows.","solutions":["Do nothing — bd sync's retry loop handles this sentinel automatically.","If surfacing it yourself, check errors.Is(err, versioncontrolops.ErrPullBehindFastForwardable) and retry the pull after a short delay.","Reduce sync concurrency or stagger sync schedules across clients if races are frequent.","If retries keep hitting it, verify the remote actually accepts pushes (the peer may be pushing faster than you can pull)."],"exampleFix":"// before\nif err := store.Pull(ctx); err != nil { return err }\n// after\nif err := store.Pull(ctx); err != nil {\n    if errors.Is(err, versioncontrolops.ErrPullBehindFastForwardable) {\n        time.Sleep(2 * time.Second)\n        return store.Pull(ctx) // benign race: re-pull fast-forwards\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isPullBehindFastForwardable(err error) bool {\n    return errors.Is(err, versioncontrolops.ErrPullBehindFastForwardable)\n}","tryCatchPattern":"if err := store.Pull(ctx); err != nil {\n    if errors.Is(err, versioncontrolops.ErrPullBehindFastForwardable) {\n        time.Sleep(time.Second)\n        return store.Pull(ctx) // benign race, self-correcting\n    }\n    return err\n}","preventionTips":["Stagger bd sync schedules across clients sharing a remote.","Treat this sentinel as retryable, never as a hard failure.","Bound retries to avoid spinning against a very chatty peer.","Log the tracking-ref hashes to confirm progress over retries."],"tags":["dolt","pull","retry","race","fast-forward"],"backgroundTag":"pull-behind-fast-forwardable","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}