{"record":{"id":"a57202faca736c47","repo":"gastownhall/beads","slug":"w-federation-peer-s-a57202","errorCode":null,"errorMessage":"%w: federation peer %s","messagePattern":"%w: federation peer (.+?)","errorType":"exception","errorClass":"storage.ErrNotFound","httpStatus":null,"severity":"info","filePath":"internal/storage/issueops/federation.go","lineNumber":77,"sourceCode":"}\n\n// GetFederationPeerInTx retrieves a federation peer by name.\n// Returns storage.ErrNotFound (wrapped) if the peer does not exist.\nfunc GetFederationPeerInTx(ctx context.Context, tx *sql.Tx, name string) (*FederationPeerRow, error) {\n\tvar row FederationPeerRow\n\tvar lastSync sql.NullTime\n\tvar username sql.NullString\n\n\terr := tx.QueryRowContext(ctx, `\n\t\tSELECT name, remote_url, username, password_encrypted, sovereignty, last_sync, created_at, updated_at\n\t\tFROM federation_peers WHERE name = ?\n\t`, name).Scan(\n\t\t&row.Peer.Name, &row.Peer.RemoteURL, &username, &row.EncryptedPwd,\n\t\t&row.Peer.Sovereignty, &lastSync, &row.Peer.CreatedAt, &row.Peer.UpdatedAt,\n\t)\n\n\tif err == sql.ErrNoRows {\n\t\treturn nil, fmt.Errorf(\"%w: federation peer %s\", storage.ErrNotFound, name)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get federation peer: %w\", err)\n\t}\n\n\tif username.Valid {\n\t\trow.Peer.Username = username.String\n\t}\n\tif lastSync.Valid {\n\t\trow.Peer.LastSync = &lastSync.Time\n\t}\n\n\treturn &row, nil\n}\n\n// ListFederationPeersInTx returns all configured federation peer rows.\nfunc ListFederationPeersInTx(ctx context.Context, tx *sql.Tx) ([]*FederationPeerRow, error) {\n\trows, err := tx.QueryContext(ctx, `","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/federation.go#L59-L95","documentation":"GetFederationPeerInTx scanned no row for the requested peer name, so the function returns storage.ErrNotFound wrapped with the peer name. This is the library's canonical not-found signal for federation peers — callers should detect it with errors.Is. It means the name simply does not exist in federation_peers, not a database failure.","triggerScenarios":"Calling GetFederationPeerInTx with a peer name that was never added, or that was deleted via RemoveFederationPeerInTx; querying on a different database than the one where the peer was registered.","commonSituations":"Typo'd peer name in CLI/config; syncing against a fresh clone whose federation_peers table is empty; peer removed by another machine before this lookup.","solutions":["Verify the peer name is correct and exists via ListFederationPeersInTx before the lookup","Call AddFederationPeerInTx to register the missing peer if it should exist","Branch on errors.Is(err, storage.ErrNotFound) to treat absence as a normal case rather than a hard failure"],"exampleFix":"// before\npeer, err := issueops.GetFederationPeerInTx(ctx, tx, name) // hard-fails on missing peer\n// after\npeer, err := issueops.GetFederationPeerInTx(ctx, tx, name)\nif errors.Is(err, storage.ErrNotFound) {\n    return createOrPromptForPeer(name) // expected case\n}\nif err != nil {\n    return err\n}","handlingStrategy":"type-guard","validationCode":"peers, err := issueops.ListFederationPeersInTx(ctx, tx)\nif err == nil {\n    for _, p := range peers {\n        if p.Peer.Name == name {\n            return nil // peer exists\n        }\n    }\n}","typeGuard":"func isPeerNotFound(err error) bool {\n    return errors.Is(err, storage.ErrNotFound)\n}","tryCatchPattern":"peer, err := issueops.GetFederationPeerInTx(ctx, tx, name)\nif isPeerNotFound(err) {\n    return handleMissingPeer(name) // normal branch, not a failure\n}\nif err != nil {\n    return err\n}","preventionTips":["Treat storage.ErrNotFound (via errors.Is) as a normal control-flow signal","Cross-check peer names with ListFederationPeersInTx in admin tooling","Standardize peer-name input through one validated config source"],"tags":["not-found","federation","go"],"backgroundTag":"record-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}