fix(ai-moderation): don't cache text-only results for messages with media
- hasMediaContent now also checks evidence.attachments from metadata (not just DB attachment records), catching the race where attachment DB rows aren't inserted yet when analysis runs. - Cache-hit guard: treat cached entries as miss when the message has media evidence in metadata, so stale 24h-freezes are avoided. - Cache-write guard: skip storing text-only analysis results for messages whose metadata shows attachments/stickers/embeds. This prevents a text-only 'clean' result (from failed vision) being frozen for 24h, blocking future re-analysis with full media context. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -151,7 +151,14 @@ function hasMediaContent(
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (target.metadata) {
|
if (target.metadata) {
|
||||||
const evidence = extractMessageMediaEvidence(target.metadata);
|
const evidence = extractMessageMediaEvidence(target.metadata);
|
||||||
if (evidence.stickers.length > 0 || evidence.embeds.length > 0) return true;
|
// Check all media types from metadata — attachments in particular are
|
||||||
|
// captured at message-creation time so they exist before the DB record.
|
||||||
|
if (
|
||||||
|
evidence.stickers.length > 0 ||
|
||||||
|
evidence.embeds.length > 0 ||
|
||||||
|
evidence.attachments.length > 0
|
||||||
|
)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (attachments?.some((a) => a.message_id === target.id)) return true;
|
if (attachments?.some((a) => a.message_id === target.id)) return true;
|
||||||
return false;
|
return false;
|
||||||
@@ -1206,7 +1213,26 @@ export async function runModerationAnalysis(
|
|||||||
// Safety: skip cache entries that are artifacts of API/parse errors.
|
// Safety: skip cache entries that are artifacts of API/parse errors.
|
||||||
// A previous bug cached error results as "flagged", causing 24h false positives.
|
// A previous bug cached error results as "flagged", causing 24h false positives.
|
||||||
// This guards against both legacy corrupt entries and any future write-path bugs.
|
// This guards against both legacy corrupt entries and any future write-path bugs.
|
||||||
if (
|
const hasMediaInMeta =
|
||||||
|
target.metadata &&
|
||||||
|
(() => {
|
||||||
|
const ev = extractMessageMediaEvidence(target.metadata);
|
||||||
|
return (
|
||||||
|
ev.attachments.length > 0 ||
|
||||||
|
ev.stickers.length > 0 ||
|
||||||
|
ev.embeds.length > 0
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (hasMediaInMeta) {
|
||||||
|
log.debug(
|
||||||
|
{
|
||||||
|
messageId: target.id,
|
||||||
|
cacheKey,
|
||||||
|
},
|
||||||
|
"Cache entry exists but message has media in metadata — treating as miss",
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
cached.flags.some((f) =>
|
cached.flags.some((f) =>
|
||||||
[
|
[
|
||||||
"analysis_api_failed",
|
"analysis_api_failed",
|
||||||
@@ -1315,6 +1341,31 @@ export async function runModerationAnalysis(
|
|||||||
// Caching a transient error would turn it into a 24h false positive.
|
// Caching a transient error would turn it into a 24h false positive.
|
||||||
if (result.status === "error") continue;
|
if (result.status === "error") continue;
|
||||||
|
|
||||||
|
// Do NOT cache text-only analysis for messages with media evidence in
|
||||||
|
// metadata (attachments, stickers, embeds). A complete analysis needs
|
||||||
|
// full media context, and caching a text-only result would prevent future
|
||||||
|
// media-aware re-analysis. The attachment DB record may not exist yet
|
||||||
|
// due to a race condition, so we check the message's own metadata field.
|
||||||
|
if (target.metadata) {
|
||||||
|
const evidence = extractMessageMediaEvidence(target.metadata);
|
||||||
|
if (
|
||||||
|
evidence.attachments.length > 0 ||
|
||||||
|
evidence.stickers.length > 0 ||
|
||||||
|
evidence.embeds.length > 0
|
||||||
|
) {
|
||||||
|
log.debug(
|
||||||
|
{
|
||||||
|
messageId: target.id,
|
||||||
|
attachments: evidence.attachments.length,
|
||||||
|
stickers: evidence.stickers.length,
|
||||||
|
embeds: evidence.embeds.length,
|
||||||
|
},
|
||||||
|
"Skipping cache for text-only result — message has media evidence in metadata",
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const cacheKey = makeTextModerationCacheKey(rawContent);
|
const cacheKey = makeTextModerationCacheKey(rawContent);
|
||||||
setCachedTextModeration(cacheKey, {
|
setCachedTextModeration(cacheKey, {
|
||||||
flags: result.flags ?? [],
|
flags: result.flags ?? [],
|
||||||
|
|||||||
Reference in New Issue
Block a user