Skip to content

Fix MaskErrors for synchronous pre-execution errors - #3968

Merged
patrick91 merged 5 commits into
strawberry-graphql:mainfrom
dextermb:fix/mask-errors
Jul 23, 2026
Merged

Fix MaskErrors for synchronous pre-execution errors#3968
patrick91 merged 5 commits into
strawberry-graphql:mainfrom
dextermb:fix/mask-errors

Conversation

@dextermb

@dextermb dextermb commented Aug 5, 2025

Copy link
Copy Markdown
Contributor

Description

This fixes MaskErrors so parsing and validation errors are also masked during synchronous execution, including when ValidationCache supplies the validation result. 🍓

The sync path previously returned its pre-execution result from inside the operation lifecycle. After-yield extension hooks could update execution_context.pre_execution_errors, but the already-created return object retained the original errors.

This update:

  • lets MaskErrors process pre_execution_errors when no execution result exists
  • finalises the synchronous pre-execution result after operation hooks complete
  • preserves the existing execution and streamed-result masking paths
  • adds regression coverage for syntax and cached validation failures

Fixes #3844.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation-only change

Validation

  • uv run pytest tests/schema/extensions/test_mask_errors.py -q — 9 passed
  • uv run pytest tests/schema/extensions/ -q — 174 passed, 10 skipped
  • uv run mypy --config-file mypy.ini — 240 source files clean
  • uv run ruff check . — passed
  • uv run ruff format --check . — 647 files formatted

Summary by Sourcery

Fix masking of pre-execution errors during synchronous execution so error details are consistently hidden, and add regression coverage and release notes for this behavior.

Bug Fixes:

  • Ensure MaskErrors also masks parsing and validation errors for synchronous execution, including when ValidationCache is used.

Enhancements:

  • Refine MaskErrors error processing to operate on a typed result-with-errors protocol shared across sync, async, and streamed executions.

Documentation:

  • Add release notes describing the fix for synchronous masking of parsing and validation errors.

Tests:

  • Add sync and async tests verifying that pre-execution parsing and validation errors are masked when using ValidationCache with MaskErrors.

@sourcery-ai

sourcery-ai Bot commented Aug 5, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactor the MaskErrors extension by extracting common error masking logic into a new helper and applying it consistently to both pre-execution and execution errors in on_operation, along with adding a corresponding release note.

File-Level Changes

Change Details Files
Extract and unify error masking logic into a helper
  • Removed obsolete _process_result method
  • Added _process_errors(list) returning processed error list
  • Reworked loop to accept any error list and return masked/unmasked errors
strawberry/extensions/mask_errors.py
Apply unified error masking in on_operation
  • Process and replace pre_execution_errors via _process_errors
  • Simplify execution result handling by checking result.errors and masking them
  • Removed ExecutionResult type check and initial_result branch
strawberry/extensions/mask_errors.py
Add release note for minor update
  • Created RELEASE.md with minor release entry for MaskErrors changes
RELEASE.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@dextermb

dextermb commented Aug 5, 2025

Copy link
Copy Markdown
Contributor Author

I've still got to properly test this, but I thought I'd open a draft pull request to get some initial feedback

@patrick91

Copy link
Copy Markdown
Member

Look alright! But I'll do a proper review once we get tests :D

@codecov

codecov Bot commented Aug 5, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.41%. Comparing base (1bbe8db) to head (446a9a1).
⚠️ Report is 397 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3968      +/-   ##
==========================================
+ Coverage   94.40%   94.41%   +0.01%     
==========================================
  Files         528      528              
  Lines       34371    34368       -3     
  Branches     1803     1801       -2     
==========================================
+ Hits        32449    32450       +1     
+ Misses       1630     1627       -3     
+ Partials      292      291       -1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Aug 5, 2025

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 34 untouched benchmarks


Comparing dextermb:fix/mask-errors (fb19639) with main (e6ffbc3)

Open in CodSpeed

@dextermb

dextermb commented Aug 5, 2025

Copy link
Copy Markdown
Contributor Author

Look alright! But I'll do a proper review once we get tests :D

Yeah, need to look into how to set them up for the extension as there weren't any originally

@patrick91

Copy link
Copy Markdown
Member

there's some here: https://github.com/strawberry-graphql/strawberry/blob/main/tests/schema/extensions/test_mask_errors.py

@Speedy1991

Speedy1991 commented Sep 15, 2025

Copy link
Copy Markdown
Contributor

Hey @dextermb are you still working on this PR? I can provide a test if needed - I'm just not sure how i can contribute to another author branch :)

Test:

@pytest.mark.asyncio
async def test_mask_errors_with_validation_error_async():

    @strawberry.type
    class Query:
        @strawberry.field
        def test_field(self) -> str:
            return 'TestField'

    schema = strawberry.Schema(query=Query, extensions=[MaskErrors()])

    query = "query { testField( }" # missing closing brace should raise a PreExecutionError (validation)

    # Use async execution to ensure we get StrawberryExecutionResult path
    result = await schema.execute(query)
    assert isinstance(result, PreExecutionError) # <<<< TODO SMELLY?
    assert result.errors is not None
    formatted_errors = [err.formatted for err in result.errors]
    assert formatted_errors == [
        {
            "locations": [{"column": 20, "line": 1}],
            "message": "Unexpected error.",
        }
    ]

But i found another code smell in https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/schema/schema.py#L602. This returns an Optional[PreExecutionError] which returns immediately an PreExecutionError but async def execute...) -> ExecutionResult should be an execution result and not an PreExeuctionError - not sure how we should handle this @patrick91?

@dextermb

dextermb commented Sep 19, 2025

Copy link
Copy Markdown
Contributor Author

Hey @dextermb are you still working on this PR?

Hi, I haven't had chance to loop back around to this. Happy to continue to contribute. If you want to chat off GitHub my Discord username is "dmb".

Work wise, I'll likely have more time on this in the coming month(s).

@patrick91 patrick91 changed the title [draft] tweak MaskErrors to handle pre_execution_errors and execution errors Fix MaskErrors for synchronous pre-execution errors 🍓 Jul 22, 2026
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Thanks for adding the RELEASE.md file!

Below is the changelog that will be used for the release.


This release fixes an issue where MaskErrors leaked parsing and validation
error details during synchronous execution.

Synchronous execution now masks pre-execution errors consistently with
asynchronous execution, including when ValidationCache is enabled.

This release was contributed by @dextermb in #3968

Additional contributors: @patrick91

@patrick91
patrick91 marked this pull request as ready for review July 22, 2026 22:30
@patrick91 patrick91 changed the title Fix MaskErrors for synchronous pre-execution errors 🍓 Fix MaskErrors for synchronous pre-execution errors Jul 22, 2026
@patrick91

Copy link
Copy Markdown
Member

@Speedy1991 can you check this? :D

@dextermb I've updated this a bit, hope that's ok!

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/schema/extensions/test_mask_errors.py" line_range="24-29" />
<code_context>
+        def test_field(self) -> str:
+            return "TestField"
+
+    schema = strawberry.Schema(
+        query=Query,
+        extensions=[ValidationCache, MaskErrors],
+    )
+
+    result = schema.execute_sync(query)
+
+    assert result.errors is not None
</code_context>
<issue_to_address>
**issue (testing):** Add an explicit regression test that exercises ValidationCache on a cache hit (same failing query executed twice).

This test only runs each invalid query once, so it never exercises the cached-validation path. To cover the regression scenario (including when `ValidationCache` returns a cached result), please add a test that runs the same invalid query twice against the same schema, e.g.:

```python
query = "query { missingField }"
result1 = schema.execute_sync(query)
result2 = schema.execute_sync(query)

for result in (result1, result2):
    assert result.errors is not None
    assert [e.message for e in result.errors] == ["Unexpected error."]
```

This validates masking on both the initial and cached validation results.
</issue_to_address>

### Comment 2
<location path="tests/schema/extensions/test_mask_errors.py" line_range="29-32" />
<code_context>
+        extensions=[ValidationCache, MaskErrors],
+    )
+
+    result = schema.execute_sync(query)
+
+    assert result.errors is not None
+    assert [error.message for error in result.errors] == ["Unexpected error."]
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Also assert that `result.data` is `None` to prove the error is truly pre-execution.

Since these tests target pre-execution errors, add an assertion that `result.data is None` to verify no resolvers ran. This will cause the test to fail if execution starts happening even though the error message remains masked.

```suggestion
    result = schema.execute_sync(query)

    assert result.data is None
    assert result.errors is not None
    assert [error.message for error in result.errors] == ["Unexpected error."]
```
</issue_to_address>

### Comment 3
<location path="RELEASE.md" line_range="14-15" />
<code_context>
+    error details.
+---
+
+This release fixes `MaskErrors` leaking parsing and validation error details
+during synchronous execution.
+
+Synchronous execution now masks pre-execution errors consistently with
</code_context>
<issue_to_address>
**suggestion (typo):** Consider rephrasing this sentence to improve grammatical clarity around "fixes `MaskErrors` leaking".

The phrase "fixes `MaskErrors` leaking parsing and validation error details" is grammatically awkward. Consider, for example: "This release fixes an issue where `MaskErrors` leaked parsing and validation error details during synchronous execution."

```suggestion
This release fixes an issue where `MaskErrors` leaked parsing and validation error details during synchronous execution.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/schema/extensions/test_mask_errors.py
Comment thread tests/schema/extensions/test_mask_errors.py
Comment thread RELEASE.md Outdated
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends error masking to synchronous parsing and validation failures. The main changes are:

  • Expose synchronous pre-execution results before operation hooks unwind.
  • Refactor masking around error-bearing result objects.
  • Add sync and async tests with ValidationCache enabled.
  • Document the fix in the release notes.

Confidence Score: 5/5

This looks safe to merge after a small test coverage cleanup.

  • The changed lifecycle exposes the returned pre-execution object before masking hooks unwind.
  • The masking path handles the concrete result types used here.
  • The new validation test enables caching but does not execute the query twice to cover a cache hit.

tests/schema/extensions/test_mask_errors.py

Important Files Changed

Filename Overview
strawberry/schema/schema.py Exposes synchronous pre-execution results to post-yield operation hooks.
strawberry/extensions/mask_errors.py Refactors error processing and masks errors on structurally compatible result objects.
tests/schema/extensions/test_mask_errors.py Adds pre-execution masking tests, but the validation cache-hit path is not exercised.
RELEASE.md Documents synchronous masking for parsing and validation errors.

Reviews (1): Last reviewed commit: "Align sync pre-execution result lifecycl..." | Re-trigger Greptile

Comment thread tests/schema/extensions/test_mask_errors.py
@patrick91
patrick91 merged commit 3ba583f into strawberry-graphql:main Jul 23, 2026
77 checks passed
@botberry

Copy link
Copy Markdown
Member

This PR was published as 0.323.2. Thank you for contributing!

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.

Flaky error handling with MaskErrors

4 participants