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

fix: skip missing records instead of failing hydration

- Add isRecordNotFoundError helper to detect permanent failures
- Handle RecordNotFound errors in profile and post hydration
- Log warning and skip missing records instead of throwing
- Prevents retries for deleted or non-existent content

Deleted posts and profiles are common in moderation contexts.
This change treats them as expected conditions rather than errors.

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

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

+17 -2
+5 -1
src/hydration/posts.service.ts
··· 3 3 import { PostsRepository } from "../database/posts.repository.js"; 4 4 import { BlobProcessor } from "../blobs/processor.js"; 5 5 import { pRateLimit } from "p-ratelimit"; 6 - import { withRetry, isRateLimitError, isNetworkError, isServerError } from "../utils/retry.js"; 6 + import { withRetry, isRateLimitError, isNetworkError, isServerError, isRecordNotFoundError } from "../utils/retry.js"; 7 7 import { logger } from "../logger/index.js"; 8 8 import { config } from "../config/index.js"; 9 9 ··· 110 110 } 111 111 } 112 112 } catch (error) { 113 + if (isRecordNotFoundError(error)) { 114 + logger.warn({ uri }, "Post record not found, skipping"); 115 + return; 116 + } 113 117 logger.error({ error, uri }, "Failed to hydrate post"); 114 118 throw error; 115 119 }
+5 -1
src/hydration/profiles.service.ts
··· 2 2 import { Database } from "duckdb"; 3 3 import { ProfilesRepository } from "../database/profiles.repository.js"; 4 4 import { pRateLimit } from "p-ratelimit"; 5 - import { withRetry, isRateLimitError, isNetworkError, isServerError } from "../utils/retry.js"; 5 + import { withRetry, isRateLimitError, isNetworkError, isServerError, isRecordNotFoundError } from "../utils/retry.js"; 6 6 import { logger } from "../logger/index.js"; 7 7 import { config } from "../config/index.js"; 8 8 ··· 108 108 109 109 logger.info({ did, handle }, "Profile hydrated successfully"); 110 110 } catch (error) { 111 + if (isRecordNotFoundError(error)) { 112 + logger.warn({ did }, "Profile record not found, skipping"); 113 + return; 114 + } 111 115 logger.error({ error, did }, "Failed to hydrate profile"); 112 116 throw error; 113 117 }
+7
src/utils/retry.ts
··· 93 93 export function isServerError(error: any): boolean { 94 94 return error?.status >= 500 && error?.status < 600; 95 95 } 96 + 97 + export function isRecordNotFoundError(error: any): boolean { 98 + return ( 99 + error?.error === "RecordNotFound" || 100 + error?.message?.includes("RecordNotFound") 101 + ); 102 + }