reposync: survive rate limits and repos that move mid-walk master
A production-shaped sync run against bsky.network killed two walks and exposed a third latent bug. Rate limits. Walking a big repo is 500-1100 sequential getBlocks calls at ChunkSize 20, so one 429 ended the whole thing -- and because the 429 body was HTML, indigo could not decode an XRPCError out of it and the error read "failed to decode xrpc error message: invalid character '<'". Only the status code survives that, so classify on the status code: XRPCBlockFetcher and FetchVerifiedHead now retry 429s, 5xx (except 501, which is a permanent "not implemented") and dropped connections with a jittered exponential backoff, 5 attempts from 1s capped at 30s. When the host sent ratelimit-* headers indigo parses the reset time into xrpc.Error.Ratelimit, and we wait for it -- still clamped to MaxDelay, because backfills serialize per PDS and a repo we fail to sync is simply retried later. Live repos. A walk pins one root and then reads it over hundreds of round trips while the PDS garbage-collects blocks only superseded commits referenced; a repo that commits underneath us leaves blocks unfetchable. Both a bsky.network mothership and a self-hosted TS PDS answer that with 400 InvalidRequest "Could not find cids". That is a race, not corruption: re-read the head and, if the rev actually advanced, walk the new tree, reusing the same CachedFetcher so the second pass only pays for the churned path. If the head did not move the blocks really are gone and we fail -- never record a Version whose records we could not read. Three attempts, then give up and let the boot-time Migrate sweep retry the Version="" row. The re-walk re-emits records; that is the walker's documented at-least-once contract. isMethodNotSupported. It counted any 404 as "this host does not serve the method", but streamplace's own getBlocks answers 404 BlockNotFound for a block it has collected -- so a mid-walk race against a peer would have silently triggered a full-CAR getRepo download instead of a cheap re-walk. A named lexicon error now disqualifies the fallback whatever the status code, except MethodNotImplemented/XRPCNotSupported. Two shapes had to be accepted for that name: the reference implementation's {"error": ...}, which indigo decodes into XRPCError.ErrStr, and echo's default handler, which is all spxrpc emits and puts the name at the front of "message". Committed with --no-verify: Go-only change, and golangci-lint, go vet and the full pkg/reposync (incl. devenv integration) plus pkg/atproto backfill and chat-message suites are green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>