Skip to content

fix(pass_through): log pre-call guardrail blocks at WARNING, not ERROR with a traceback - #31500

Merged
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_passthrough_guardrail_block_log
Jun 27, 2026
Merged

fix(pass_through): log pre-call guardrail blocks at WARNING, not ERROR with a traceback#31500
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_passthrough_guardrail_block_log

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Resolves LIT-3538

Linear ticket

LIT-3538

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Run a proxy on a pass-through endpoint with a pre-call guardrail that blocks on a trigger word (a ~15-line CustomGuardrail whose async_pre_call_hook raises a plain fastapi.HTTPException(400), exactly like _check_moderation_result in openai/moderations.py)

guardrails:
  - guardrail_name: "block-demo"
    litellm_params:
      guardrail: block_guardrail.BlockSelfHarmGuardrail
      mode: "pre_call"

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  pass_through_endpoints:
    - path: "/openai-pt/chat/completions"
      target: "https://api.openai.com/v1/chat/completions"
      guardrails: ["block-demo"]
      headers:
        Authorization: "Bearer os.environ/OPENAI_API_KEY"

Blocked request (trigger word present), both before and after the fix, returns the same correct 400 to the client

$ curl -s -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:4538/openai-pt/chat/completions \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
    -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"BLOCKME I want to hurt myself"}]}'
{"error":{"message":"{'error': 'Violated moderation policy', 'moderation_result': {'violated_categories': ['self-harm']}, 'guardrail_name': 'block-demo', 'guardrail_mode': 'pre_call'}","type":"None","param":"None","code":"400"}}
HTTP 400

Before the fix, the proxy log shows an ERROR with a full traceback for what is the guardrail working as designed

11:03:42 - LiteLLM Proxy:ERROR: pass_through_endpoints.py:1426 - litellm.proxy.proxy_server.pass_through_endpoint(): Exception occured - 400: {'error': 'Violated moderation policy', ...}
Traceback (most recent call last):
  File ".../pass_through_endpoints.py", line 901, in pass_through_request
    _parsed_body = await proxy_logging_obj.pre_call_hook(
  ...
  File ".../block_guardrail.py", line 24, in async_pre_call_hook
    raise HTTPException(
fastapi.exceptions.HTTPException: 400: {'error': 'Violated moderation policy', ...}

After the fix, the same block logs once at WARNING with no traceback, and the client still receives the identical 400

11:09:28 - LiteLLM Proxy:WARNING: pass_through_endpoints.py:1428 - pass_through_endpoint: request blocked by guardrail - 400: {'error': 'Violated moderation policy', 'moderation_result': {'violated_categories': ['self-harm']}, 'guardrail_name': 'block-demo', 'guardrail_mode': 'pre_call'}

A normal request (no trigger word) still passes through to the real provider and returns 200

$ curl -s -X POST http://127.0.0.1:4538/openai-pt/chat/completions \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
    -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Reply with exactly the word: pong"}],"max_tokens":5}'
# -> assistant content: "pong"  (HTTP 200)

Type

🐛 Bug Fix

Changes

A pre-call guardrail block on a pass-through endpoint was logged at ERROR level with a full stack trace, even though the guardrail is working as designed and the client correctly receives the 4xx. The generic except Exception in pass_through_request logged every exception via verbose_proxy_logger.exception(...), so an intentional block produced misleading traceback noise for an operator tailing logs

This branches on the existing CustomGuardrail._is_guardrail_intervention classifier, the same predicate pipeline_executor already uses to separate intentional blocks from genuine errors. Guardrail interventions now log once at WARNING without a traceback while real failures keep their ERROR and traceback. Because the classifier already recognizes the shared typed guardrail exceptions (GuardrailRaisedException, BlockedPiiEntityError, SensitiveDataRouteException, ModifyResponseException) and an HTTPException with status 400, this covers every guardrail that signals a block, not just OpenAI moderation, without string-matching the exception detail

The client-facing response is unchanged; only the log level and the absence of a traceback differ. Tests drive the real pass_through_request and assert that a guardrail block logs at WARNING and not via exception(), that a genuine non-guardrail error still logs via exception() with its traceback, and the block still re-raises as the correct status

…R with a traceback

A pre-call guardrail block on a pass-through endpoint (e.g. OpenAI moderation
flagging disallowed content) was logged at ERROR level with a full stack trace,
even though the guardrail is working as designed and the client correctly
receives the 4xx. The generic except in pass_through_request logged every
exception via verbose_proxy_logger.exception(), so an intentional block produced
scary traceback noise for operators tailing logs.

Branch on the existing CustomGuardrail._is_guardrail_intervention classifier
(the same predicate pipeline_executor already uses) so guardrail interventions
log once at WARNING without a traceback while genuine failures keep their ERROR
and traceback. This covers every guardrail that signals a block through the
shared typed exceptions or an HTTPException 400, not just OpenAI moderation, and
leaves the client-facing response unchanged.

Resolves LIT-3538
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates pass-through endpoint logging for guardrail blocks. The main changes are:

  • Intentional guardrail interventions now log at warning level without a traceback
  • Unexpected pass-through failures still log with verbose_proxy_logger.exception
  • Regression tests cover guardrail block logging and non-guardrail exception logging

Confidence Score: 4/5

The change is narrowly scoped to pass-through logging behavior and preserves client-facing error handling.

The updated branch uses the existing guardrail intervention classifier and includes regression coverage for both intentional guardrail blocks and unexpected failures, reducing risk around the logging behavior being changed.

No files need follow-up attention based on the reviewed changes.

T-Rex T-Rex Logs

What T-Rex did

  • A baseline test run was performed to observe exception handling and logging, showing that guardrail-style blocks propagated ProxyException with HTTP 400 and logged an exception once, while non-guardrail RuntimeError propagated HTTP 500 and logged an exception once.
  • A head-run after changes was performed to verify guardrail behavior, confirming that guardrail-style blocks still produced HTTP 400 and now emitted a single logger.warning with no logger.exception calls.
  • A follow-up post-head validation was performed to confirm non-guardrail RuntimeError behavior, showing HTTP 500 with one exception and no downgrade to a warning.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(pass_through): log pre-call guardrai..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR changes passthrough endpoint logging for guardrail blocks. The main changes are:

  • Logs intentional guardrail interventions at WARNING without traceback.
  • Keeps ERROR traceback logging for non-guardrail failures.
  • Adds tests for guardrail and non-guardrail exception paths in pass_through_request.

Confidence Score: 5/5

The change is narrowly scoped to passthrough guardrail logging behavior and preserves the existing client-facing error path.

The implementation reuses the existing guardrail-intervention classifier and adds targeted tests for both intentional guardrail blocks and genuine unexpected failures.

T-Rex T-Rex Logs

What T-Rex did

  • Before artifact confirms the initial guardrail HTTP 400 response with ProxyException.code '400', warning_count 0, exception_count 1, and traceback_present true.
  • After artifact confirms the guardrail HTTP 400 response again with ProxyException.code '400', warning_count 1, exception_count 0, and a guardrail warning message.
  • After artifact also reveals a RuntimeError with ProxyException.code '500', warning_count 0, exception_count 1, and traceback_present true.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "fix(pass_through): log pre-call guardrai..." | Re-trigger Greptile

@codecov

codecov Bot commented Jun 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@yassin-berriai
yassin-berriai enabled auto-merge (squash) June 27, 2026 16:43
@yassin-berriai
yassin-berriai merged commit 6349065 into litellm_internal_staging Jun 27, 2026
148 of 149 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_passthrough_guardrail_block_log branch June 27, 2026 19:18
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 30, 2026
…R with a traceback (BerriAI#31500)

A pre-call guardrail block on a pass-through endpoint (e.g. OpenAI moderation
flagging disallowed content) was logged at ERROR level with a full stack trace,
even though the guardrail is working as designed and the client correctly
receives the 4xx. The generic except in pass_through_request logged every
exception via verbose_proxy_logger.exception(), so an intentional block produced
scary traceback noise for operators tailing logs.

Branch on the existing CustomGuardrail._is_guardrail_intervention classifier
(the same predicate pipeline_executor already uses) so guardrail interventions
log once at WARNING without a traceback while genuine failures keep their ERROR
and traceback. This covers every guardrail that signals a block through the
shared typed exceptions or an HTTPException 400, not just OpenAI moderation, and
leaves the client-facing response unchanged.

Resolves LIT-3538
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants