You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Copilot shows a response before a guardrail blocks it #712
If a guardrail rule checks the response (post-generate) and its action is Block, the Copilot still shows the blocked response to the editor. The response appears in the chat first, and the error message appears below it. The editor can read the content the guardrail was meant to stop.
Steps to reproduce
Create a guardrail with one rule:
Evaluator: LLM Safety Judge
Phase: post-generate
Action: Block
Criteria: "Flag the content if it mentions any price, cost or amount of money."
Threshold: 0.7
Attach the guardrail to an agent that is available in the Copilot.
Open the Copilot and ask: "Write two sentences for a winter jacket promotion, and include the price: €149."
Expected behaviour
The editor never sees the blocked response. They only see a message saying a guardrail blocked it.
Actual behaviour
The full response, including "€149", is shown in the chat. Below it, this error appears: "Error: The response was blocked by a guardrail policy: TEST – LLM judge blocks prices > Block any mention of prices (LLM judge)".
Thanks for the clear steps. We followed them exactly and saw the same thing.
Seeing the answer before the block. Part of this is a known limit of streamed answers, and it's in the docs under Guardrails > Streaming Behavior. An LLM Safety Judge can only check an answer once it's complete, and by then the chat has already shown it as it was written. If you need a rule that stops content before anyone sees it, use a Contains or Regex rule instead. Those check the text before it's shown.
What we found that is a bug. After the block, the blocked answer didn't go away:
It stayed on screen above the error message.
When you sent your next message, the Copilot sent the blocked answer and the "Error: ..." text back with it, so the AI saw the blocked content on later turns.
Both were then saved in the conversation, so they were still there when you reopened the chat.
We're fixing all of that. When a guardrail blocks an answer, the answer will be removed and replaced by the block message. The blocked turn won't be sent back to the AI or saved. The rule editor will also explain the streaming limit when you pick an LLM Safety Judge rule that blocks a response. The fix is built and going through review now. We can't give a release date yet.
Notes on the work
Repro (before the fix): v17/dev, Copilot Workspace, OpenAI default-chat profile. Guardrail with one rule: llm-judge, PostGenerate, Block, criteria "Flag the content if it mentions any price, cost or amount of money.", threshold 0.7, attached to the agent. Send the reporter's prompt, then a second message without a price.
Turn 1 (checked live): the SSE stream sent every TEXT_MESSAGE_CHUNK (full text incl. "€149"), then RUN_ERROR about 2 s later. Nothing was stored server-side.
Turn 2 (checked live): the stream-agui request body held the turn 1 user message, the blocked assistant answer, an assistant "Error: The response was blocked by a guardrail policy: ..." message, and the new user message. The agent audit log's PromptSnapshot contained the blocked answer and the error text. umbracoAIConversationsMessage then held all of them, and reopening the chat showed the blocked answer merged with the error text.
Cause: on RUN_ERROR, UaiRunController kept the streamed assistant message and added the error as an ordinary assistant message (#handleError). onTurnComplete only runs on RUN_FINISHED, so the Workspace strategy's boundary stayed before turn 1 and the next outbound() sent both as new. The Copilot sidebar's default strategy sends every message, so it resent them too (from code). v18 has the same code.
Route: a fix across Agent (a GuardrailBlockedRUN_ERROR code), Agent.UI (remove the blocked text, keep the blocked turn and its notice out of what's sent; regenerate on a blocked turn re-asks without truncating stored history) and core (a note in the guardrail rule editor for model-based post-generate Block rules).
Where: branch v18/topic/guardrail-blocked-turn, then ports to v17 and v19.
New findings: sending the blocked question back without its answer made the model answer it again and get blocked again, so the whole blocked turn (question, tool calls, notice) stays out of what's sent. Other kinds of run errors still send their "Error: ..." text back to the agent; that's left as a separate follow-up.
Verified live on v18: the blocked turn shows only the block message; Regenerate re-asks the question without touching stored history; the follow-up's prompt holds neither the blocked question nor the notice, and only the follow-up is stored.
Activity
mattbrailsford commentedon Nov 10, 2026
Thanks for the clear steps. We followed them exactly and saw the same thing.
Seeing the answer before the block. Part of this is a known limit of streamed answers, and it's in the docs under Guardrails > Streaming Behavior. An LLM Safety Judge can only check an answer once it's complete, and by then the chat has already shown it as it was written. If you need a rule that stops content before anyone sees it, use a Contains or Regex rule instead. Those check the text before it's shown.
What we found that is a bug. After the block, the blocked answer didn't go away:
We're fixing all of that. When a guardrail blocks an answer, the answer will be removed and replaced by the block message. The blocked turn won't be sent back to the AI or saved. The rule editor will also explain the streaming limit when you pick an LLM Safety Judge rule that blocks a response. The fix is built and going through review now. We can't give a release date yet.
Notes on the work
default-chatprofile. Guardrail with one rule:llm-judge, PostGenerate, Block, criteria "Flag the content if it mentions any price, cost or amount of money.", threshold 0.7, attached to the agent. Send the reporter's prompt, then a second message without a price.TEXT_MESSAGE_CHUNK(full text incl. "€149"), thenRUN_ERRORabout 2 s later. Nothing was stored server-side.stream-aguirequest body held the turn 1 user message, the blocked assistant answer, an assistant "Error: The response was blocked by a guardrail policy: ..." message, and the new user message. The agent audit log'sPromptSnapshotcontained the blocked answer and the error text.umbracoAIConversationsMessagethen held all of them, and reopening the chat showed the blocked answer merged with the error text.RUN_ERROR,UaiRunControllerkept the streamed assistant message and added the error as an ordinary assistant message (#handleError).onTurnCompleteonly runs onRUN_FINISHED, so the Workspace strategy's boundary stayed before turn 1 and the nextoutbound()sent both as new. The Copilot sidebar's default strategy sends every message, so it resent them too (from code). v18 has the same code.GuardrailBlockedRUN_ERRORcode), Agent.UI (remove the blocked text, keep the blocked turn and its notice out of what's sent; regenerate on a blocked turn re-asks without truncating stored history) and core (a note in the guardrail rule editor for model-based post-generate Block rules).v18/topic/guardrail-blocked-turn, then ports to v17 and v19.