A tool for tailing a labelers' firehose, rehydrating, and storing records for future analysis of moderation decisions.

fix: re-hydrate existing profiles and prevent infinite loops

- Re-hydrate profiles with NULL avatar_cid/banner_cid fields
- Use empty string as sentinel for "no avatar/banner" vs NULL for "not checked"
- Add debug logging to inspect profile record structure
- Skip blob processing for empty CID strings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+18 -4
src
+18 -4
src/hydration/profiles.service.ts
··· 41 41 async hydrateProfile(did: string): Promise<void> { 42 42 try { 43 43 const existingProfile = await this.profilesRepo.findByDid(did); 44 - if (existingProfile) { 45 - logger.debug({ did }, "Profile already hydrated, skipping"); 44 + const needsRehydration = existingProfile && (existingProfile.avatar_cid === null || existingProfile.banner_cid === null); 45 + 46 + if (existingProfile && !needsRehydration) { 47 + logger.debug({ did }, "Profile already fully hydrated, skipping"); 46 48 return; 49 + } 50 + 51 + if (needsRehydration) { 52 + logger.debug({ did }, "Re-hydrating profile to fetch avatar/banner CIDs"); 47 53 } 48 54 49 55 const profileResponse = await this.limit(() => ··· 76 82 77 83 if (profileResponse.success && profileResponse.data.value) { 78 84 const record = profileResponse.data.value as any; 85 + logger.debug({ did, record }, "Profile record structure"); 79 86 displayName = record.displayName; 80 87 description = record.description; 81 88 82 89 if (record.avatar?.ref?.$link) { 83 90 avatarCid = record.avatar.ref.$link; 91 + } else { 92 + avatarCid = ""; 84 93 } 94 + 85 95 if (record.banner?.ref?.$link) { 86 96 bannerCid = record.banner.ref.$link; 97 + } else { 98 + bannerCid = ""; 87 99 } 100 + 101 + logger.debug({ did, avatarCid, bannerCid, hasAvatar: !!record.avatar, hasBanner: !!record.banner }, "Extracted CIDs from profile record"); 88 102 } 89 103 90 104 const profileLookup = await this.limit(() => ··· 120 134 banner_cid: bannerCid, 121 135 }); 122 136 123 - if (avatarCid) { 137 + if (avatarCid && avatarCid !== "") { 124 138 try { 125 139 await this.blobProcessor.processBlobs(`profile://${did}/avatar`, [ 126 140 { ··· 139 153 } 140 154 } 141 155 142 - if (bannerCid) { 156 + if (bannerCid && bannerCid !== "") { 143 157 try { 144 158 await this.blobProcessor.processBlobs(`profile://${did}/banner`, [ 145 159 {