{"record":{"id":"510598bb94210d4c","repo":"gastownhall/beads","slug":"scan-federation-peer-w","errorCode":null,"errorMessage":"scan federation peer: %w","messagePattern":"scan federation peer: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/federation.go","lineNumber":114,"sourceCode":"\t\tSELECT name, remote_url, username, password_encrypted, sovereignty, last_sync, created_at, updated_at\n\t\tFROM federation_peers ORDER BY name\n\t`)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"list federation peers: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar peers []*FederationPeerRow\n\tfor rows.Next() {\n\t\tvar row FederationPeerRow\n\t\tvar lastSync sql.NullTime\n\t\tvar username sql.NullString\n\n\t\tif err := rows.Scan(\n\t\t\t&row.Peer.Name, &row.Peer.RemoteURL, &username, &row.EncryptedPwd,\n\t\t\t&row.Peer.Sovereignty, &lastSync, &row.Peer.CreatedAt, &row.Peer.UpdatedAt,\n\t\t); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scan federation peer: %w\", err)\n\t\t}\n\n\t\tif username.Valid {\n\t\t\trow.Peer.Username = username.String\n\t\t}\n\t\tif lastSync.Valid {\n\t\t\trow.Peer.LastSync = &lastSync.Time\n\t\t}\n\n\t\tpeers = append(peers, &row)\n\t}\n\treturn peers, rows.Err()\n}\n\n// RemoveFederationPeerInTx deletes a federation peer by name.\nfunc RemoveFederationPeerInTx(ctx context.Context, tx *sql.Tx, name string) error {\n\t_, err := tx.ExecContext(ctx, \"DELETE FROM federation_peers WHERE name = ?\", name)\n\tif err != nil {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/federation.go#L96-L132","documentation":"A row from the federation_peers listing could not be scanned into FederationPeerRow; the error is wrapped as \"scan federation peer\". This almost always means the database schema doesn't match the code's expectations (column type or count mismatch) or a row holds a value incompatible with the destination field (e.g. NULL where not allowed).","triggerScenarios":"rows.Scan fails for a peer row: column count/type mismatch after a schema change, or an unexpected NULL/data type in name, remote_url, username, password_encrypted, sovereignty, last_sync, created_at, or updated_at.","commonSituations":"Upgrading/downgrading beads so the code and stored schema diverge; manual schema edits; corrupted or hand-inserted rows with unexpected NULLs.","solutions":["Check the wrapped scan error for which column/type mismatched","Run migrations so the stored schema matches this binary's expectations","Inspect the offending row and fix unexpected NULLs or data types","Rebuild the peer record: RemoveFederationPeerInTx then AddFederationPeerInTx if a row is corrupt"],"exampleFix":"// before\n// schema has old column: password (plaintext) but code scans password_encrypted\npeers, err := issueops.ListFederationPeersInTx(ctx, tx) // scan mismatch\n// after\nif err := migrate(ctx, db); err != nil { // renames password -> password_encrypted\n    return err\n}\npeers, err := issueops.ListFederationPeersInTx(ctx, tx)","handlingStrategy":"try-catch","validationCode":"// verify expected columns before scanning\nrows, err := tx.QueryContext(ctx, `SELECT name, remote_url, username, password_encrypted, sovereignty, last_sync, created_at, updated_at FROM federation_peers LIMIT 1`)","typeGuard":null,"tryCatchPattern":"peers, err := issueops.ListFederationPeersInTx(ctx, tx)\nif err != nil {\n    if strings.Contains(err.Error(), \"Scan\") || strings.Contains(err.Error(), \"converting\") {\n        return fmt.Errorf(\"schema drift detected; run migrations: %w\", err)\n    }\n    return err\n}","preventionTips":["Run migrations on every deploy so schema and binary match","Avoid manual schema edits; treat migration files as the only schema source","Null-check rows inserted outside the library before listing"],"tags":["database","sql","schema","go"],"backgroundTag":"schema-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}