Did you check docs and existing issues?
Python version (python --version)
Python 3.13.12
Operating system/version
MacOS 26.3.1
NeMo-Guardrails version (if you must use a specific version and not the latest
No response
Describe the bug
In Colang v2, run output rails in guardrails.co sets $output_rails_in_progress = True before invoking output rails and clears it only on normal completion. If an output rail calls abort (e.g. after a rejection message), the flag is never reset. Subsequent bot say calls skip output rails because _bot_say only runs them when $output_rails_in_progress is false.
This affects any v2 output rail that can abort.
Steps To Reproduce
- Use Colang v2 with
import guardrails.
- Define an output rail that calls
bot say then abort on failure (minimal example below).
- In one session: trigger the rail → reject → send a new user message that should trigger the rail again.
- Observe the subsequent bot response bypasses output rails.
import core
import guardrails
flow output rails $output_text
if $output_text == "BLOCKME"
bot say "Rejected."
abort
@active
flow handle user messages
while True
user said something
bot say "BLOCKME"
flow main
activate handle user messages
wait indefinitely
Run test: pytest test_output_rails_abort_multiturn.py -v
test_output_rails_abort_multiturn.py
Expected Behavior
Turn 1 ("hello"): bot=['Rejected.'] rail_fired=True
Turn 2 ("hi again"): bot=['Rejected.'] rail_fired=True
Turn 3 ("one more"): bot=['Rejected.'] rail_fired=True
Output rail fires on every turn, blocking BLOCKME and returning Rejected. each time
Actual Behavior
Turn 1 ("hello"): bot=['Rejected.'] rail_fired=True
Turn 2 ("hi again"): bot=['BLOCKME'] rail_fired=False
Turn 3 ("one more"): bot=['BLOCKME'] rail_fired=False
Output rail fires only on the first turn. On all subsequent turns, $output_rails_in_progress is stuck at True (never reset after the abort), so _bot_say skips the await run output rails call entirely and passes BLOCKME through unfiltered.
Root cause:
In guardrails.co [lines 88-99], run output rails sets $output_rails_in_progress = True before calling await output rails $output_text. When the output rail aborts, execution never reaches $output_rails_in_progress = False at the end of the flow, leaving the flag permanently stuck.
Suggested fix
Ensure $output_rails_in_progress is cleared even when output rails abort e.g. reset in guardrails.co after the rail call, or reset on new user input in _user_said as a safety net.
Did you check docs and existing issues?
Python version (python --version)
Python 3.13.12
Operating system/version
MacOS 26.3.1
NeMo-Guardrails version (if you must use a specific version and not the latest
No response
Describe the bug
In Colang v2,
run output railsin guardrails.co sets$output_rails_in_progress = Truebefore invoking output rails and clears it only on normal completion. If an output rail calls abort (e.g. after a rejection message), the flag is never reset. Subsequentbot saycalls skip output rails because_bot_sayonly runs them when$output_rails_in_progressisfalse.This affects any v2 output rail that can
abort.Steps To Reproduce
import guardrails.bot saythenaborton failure (minimal example below).Run test:
pytest test_output_rails_abort_multiturn.py -vtest_output_rails_abort_multiturn.py
Expected Behavior
Output rail fires on every turn, blocking
BLOCKMEand returningRejected.each timeActual Behavior
Output rail fires only on the first turn. On all subsequent turns,
$output_rails_in_progressis stuck atTrue(never reset after the abort), so_bot_sayskips the await run output rails call entirely and passesBLOCKMEthrough unfiltered.Root cause:
In guardrails.co [lines 88-99], run output rails sets
$output_rails_in_progress = Truebefore callingawait output rails $output_text. When the output rail aborts, execution never reaches$output_rails_in_progress = Falseat the end of the flow, leaving the flag permanently stuck.Suggested fix
Ensure
$output_rails_in_progressis cleared even when output rails abort e.g. reset in guardrails.co after the rail call, or reset on new user input in_user_saidas a safety net.