Discourse AI: Gemini request blocked via promptFeedback.blockReason is treated as an empty reply and never translated

Description

When Google’s Gemini API refuses a request with promptFeedback.blockReason, Discourse AI handles the response as a successful but empty reply. In AI translation this has two effects: the topic or post never receives a detected language, so it is never translated, and the backfill sends the same blocked request again on later runs. Nothing indicates to an administrator that a block occurred.

Observed on a self-hosted site running release/2026.7 (2026.7.3, commit f1caa6321287f918644fba9ff8943579fdcf027a) with the bundled discourse-ai plugin. The request was the language detection of a topic title, using the built-in locale detector agent on gemini-3.5-flash-lite. The title was a short, harmless sentence in Swedish. The same title was translated by gemini-3.8-flash without a block.

Google answered with HTTP 200 and a single stream event with no candidates:

data: {"promptFeedback": {"blockReason": "PROHIBITED_CONTENT"},"usageMetadata": {"promptTokenCount": 417,"totalTokenCount": 417,"promptTokensDetails": [{"modality": "TEXT","tokenCount": 417}],"serviceTier": "standard"},"modelVersion": "gemini-3.5-flash-lite","responseId": "..."}

The request already carries BLOCK_NONE for the four safety categories that Discourse AI sets, so this block is not one those settings influence. The audit log records response_status 200.

Root cause

  1. DiscourseAi::Completions::Endpoints::Gemini#decode_chunk reads only candidates from each stream event. promptFeedback is never inspected, so a blocked request produces an empty result with no error.
  2. DiscourseAi::Translation::LanguageDetector#detect returns nil when the reply does not match its language-tag pattern, which is the case for an empty reply. The topic or post keeps locale NULL.
  3. Jobs::TopicsLocaleDetectionBackfill selects topics with locale NULL on every run and has no attempt limit, so by reading the code the blocked request is repeated every five minutes for as long as the topic is in the backfill window. Posts are limited to two detection attempts a day by the relocalize quota, and are retried each day.
  4. Translation candidates require a detected locale, so the item is never translated.

The same code is present on main as of 2026-10-03.

Suggested fix

  1. Detect promptFeedback.blockReason in the Gemini endpoint and raise or log a distinct, visible error that names the reason.
  2. Record the failure against the topic or post so the backfill stops repeating a request that will be blocked again, or apply an attempt limit to topic detection as already exists for posts.
  3. Surface blocked items on the translation admin page, so an administrator can set the language by hand.

Reproduce

The block depends on Google’s filter and cannot be forced with a known input. Affected calls can be found in the audit log:

SELECT id, created_at, language_model, post_id, topic_id, response_status
FROM ai_api_audit_logs
WHERE feature_name = 'translation'
  AND raw_response_payload LIKE '%blockReason%'
ORDER BY created_at

Workaround

Setting the topic’s language by hand lets translation proceed. On this site the translation model did not block the same text.

thanks for the report @Sailor :+1:

will be fixed by FIX: Handle Gemini errors and blocked prompts reported in successful responses - Pull Request #44262 - discourse/discourse - GitHub

1 like

Thanks for the quick fix. One part of this report remains open after #44262, as far as the diff shows.

With the change, a blocked detection is logged, and for topics it is limited to two attempts a day. The item itself still ends up without a locale, so it is never translated and stays counted as awaiting detection indefinitely. Nothing in the admin interface shows which items these are.

Three cases came up during one backfill of about 6,700 posts and their topics on this site, all on gemini-3.5-flash-lite with PROHIBITED_CONTENT, and all harmless: a topic title, a one-sentence post, and a post consisting of a single link.

Two possible remedies:

  1. A fallback locale when detection is blocked, for example the author’s interface locale, or for a reply the locale of its topic. In the one case here where the locale was then set by hand and the item left to the backfill, the translation model translated it without objection.
  2. Failing that, a list or count of items whose detection was blocked on the translation admin page, so that the locale can be set manually.