Skip to content

fix: redact model output from JSON validation errors - #4208

Closed
abhay-codes07 wants to merge 1 commit into
openai:mainfrom
abhay-codes07:fix/redact-model-output-validation-errors
Closed

fix: redact model output from JSON validation errors#4208
abhay-codes07 wants to merge 1 commit into
openai:mainfrom
abhay-codes07:fix/redact-model-output-validation-errors

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

Summary

agents.util._json.validate_json formats the offending model output into its ModelBehaviorError message and chains the pydantic ValidationError, which repeats the same payload in its own input_value. Neither is gated on _debug.DONT_LOG_MODEL_DATA, which is True by default, so structured output that fails schema validation reaches the exception, its cause chain, and any traceback the application logs.

This is the model side of the tool argument redaction in #4182, and one change covers the three callers that pass model generated JSON: output schema validation (any agent with an output_type), handoff input validation, and its realtime equivalent.

Before, with the default flag:

message: Invalid JSON when parsing {"answer": "SUPER_SECRET_VALUE_9f3a"} for TypeAdapter(Structured); 1 validation error for Structured
count
  Field required [type=missing, input_value={'answer': 'SUPER_SECRET_VALUE_9f3a'}, input_type=dict]
cause  : ValidationError

After:

message: Invalid JSON when parsing model output for TypeAdapter(Structured)
cause  : None

The fix follows the same shape as #4182: keep the detailed error when the flag allows it, and otherwise raise outside the except block so neither __cause__ nor __context__ keeps the ValidationError reachable. Suppressing only the message would not be enough, because the chained error carries the payload independently.

Scope notes:

  • Diagnostics are unchanged with OPENAI_AGENTS_DONT_LOG_MODEL_DATA=0: same message, same chained ValidationError.
  • The SpanError raised alongside already used data={}, so tracing was correct and is untouched.
  • DONT_LOG_MODEL_DATA is the right flag for all three callers because the payload is LLM output in every case, including handoff arguments.

Test plan

Four tests in tests/test_error_logging_redaction.py, alongside the existing #4182 cases:

  • test_output_schema_validation_error_redacts_payload_when_model_data_disabled
  • test_output_schema_validation_error_preserves_diagnostics_when_model_data_enabled
  • test_handoff_input_validation_error_redacts_payload_when_model_data_disabled
  • test_run_surfaces_redacted_output_validation_error, an end to end run through Runner.run

Each redaction test asserts the payload is absent from the message and that __cause__ and __context__ are both None, so the check cannot pass by suppressing the message alone.

Three fail on main and pass with the fix. The diagnostics test passes in both runs, which is the control that the flag still enables the detailed error:

# main
FAILED tests/test_error_logging_redaction.py::test_output_schema_validation_error_redacts_payload_when_model_data_disabled
FAILED tests/test_error_logging_redaction.py::test_handoff_input_validation_error_redacts_payload_when_model_data_disabled
FAILED tests/test_error_logging_redaction.py::test_run_surfaces_redacted_output_validation_error
3 failed, 1 passed, 67 deselected

Verification from the repository root:

Command Result
make format clean
make lint all checks passed
make mypy 5 errors, all pre-existing on main, none in the touched files
make pyright 1 error, pre-existing on main (src/agents/sandbox/util/tar_utils.py:161)
uv run pytest tests/test_error_logging_redaction.py 71 passed
make tests 5805 passed

The full suite run was done on Windows, where some sandbox symlink and tracing timing tests fail independently of this change. I diffed the failing set against a clean main checkout in the same environment. This branch has one fewer failure than the baseline, and the difference is test_tracing_errors_streamed.py::test_max_turns_exceeded, a timing test that flakes on main on its own. Nothing else differs in either direction.

Issue number

Closes #4207

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

The verification script is a bash script that shells out to make. I ran the underlying steps individually instead, with the results above.

validate_json embedded the offending JSON in its ModelBehaviorError message and
chained the pydantic ValidationError, which repeats the same payload in its own
input_value. Both surfaced regardless of DONT_LOG_MODEL_DATA, so structured
output that failed schema validation leaked into the exception, its cause chain
and any traceback the application logged.

This is the model side of the tool argument redaction added in openai#4182, and it
covers the three callers that pass model generated JSON: output schema
validation, handoff input validation and its realtime equivalent.

Follow the same shape as that fix: keep the detailed error when the flag allows
it, and otherwise raise outside the except block so neither __cause__ nor
__context__ keeps the ValidationError reachable. The span error was already
payload free and is unchanged.
Copilot AI review requested due to automatic review settings August 5, 2026 11:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eac6d33bf3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/util/_json.py
# raised outside the except block on purpose: chaining it would keep the ValidationError
# reachable through __cause__ and __context__, and that error embeds the offending model
# output in its own message.
raise ModelBehaviorError(f"Invalid JSON when parsing model output for {type_adapter}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove payload from redacted traceback locals

When model-data logging is disabled, this removes the message/cause leak but the redacted ModelBehaviorError traceback still includes the validate_json frame, whose locals contain json_str with the raw model output. Any traceback renderer or telemetry integration that captures locals can still expose the payload in the default redacted path; raise from a helper that never receives the payload or clear sensitive locals before raising.

AGENTS.md reference: AGENTS.md:L95-L95

Useful? React with 👍 / 👎.

Comment thread src/agents/util/_json.py
Comment on lines +36 to +39
if not _debug.DONT_LOG_MODEL_DATA:
raise ModelBehaviorError(
f"Invalid JSON when parsing {json_str} for {type_adapter}; {e}"
) from e

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Respect tool redaction for handoff JSON

When this helper is reached from handoff(), json_str is the handoff tool-call arguments, so an app that enables model diagnostics but keeps tool-data redaction on (DONT_LOG_MODEL_DATA=False, DONT_LOG_TOOL_DATA=True) still gets the raw handoff payload and chained Pydantic ValidationError. Use a caller-specific or mixed model/tool redaction policy here instead of keying every validate_json failure only off the model flag.

AGENTS.md reference: AGENTS.md:L95-L95

Useful? React with 👍 / 👎.

Comment thread src/agents/util/_json.py
# raised outside the except block on purpose: chaining it would keep the ValidationError
# reachable through __cause__ and __context__, and that error embeds the offending model
# output in its own message.
raise ModelBehaviorError(f"Invalid JSON when parsing model output for {type_adapter}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scrub raw responses from redacted run errors

For a Runner.run() structured-output validation failure with model-data logging disabled, this redacted ModelBehaviorError is later populated with run_data, and that RunErrorDetails.raw_responses still contains the original model message text. Any handler or telemetry exporter that inspects the exception object can therefore recover the same payload even though the message and cause were redacted; mark these redacted validation errors so runner error details omit or sanitize raw model responses.

AGENTS.md reference: AGENTS.md:L95-L95

Useful? React with 👍 / 👎.

@seratch

seratch commented Aug 5, 2026

Copy link
Copy Markdown
Member

Thanks for the contribution. The underlying issue is valid, but the current patch still leaves model-generated handoff arguments exposed when tool-data redaction remains enabled, and the payload remains reachable through SDK traceback locals. These are part of the same default redaction contract, including the synchronous and asynchronous Realtime paths.

We are going to supersede this PR with a maintainer patch that applies the combined model/tool policy to handoffs and clears redacted traceback frames at the relevant Runner and Realtime boundaries. The broader RunErrorDetails.raw_responses sanitization is not needed because the failing current response is not appended before turn processing returns.

@seratch seratch closed this Aug 5, 2026
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.

Model output leaks into JSON validation errors despite DONT_LOG_MODEL_DATA

3 participants