Skip to content

perf: skip teardown analysis when no disposable members need cleanup - #6780

Merged
thomhurst merged 1 commit into
mainfrom
perf/disposable-empty-scan
Sep 11, 2026
Merged

thomhurst merged 1 commit into
mainfrom
perf/disposable-empty-scan

Conversation

@thomhurst

@thomhurst thomhurst commented Sep 11, 2026

Copy link
Copy Markdown
Owner

DisposableFieldPropertyAnalyzer currently scans every method for teardown calls even when field, property, constructor, and setup analysis found no disposable members. This can resolve hundreds of thousands of invocation operations that cannot affect any diagnostic.

Return after setup analysis when the tracked-member collection is empty. Classes with disposable members continue through the existing teardown and reporting paths; instance and static analysis remain separate.

Measured with BenchmarkDotNet 0.15.8 over 10,000 tests in 100 classes, using the actual baseline (b43fecac6d) and PR analyzer assemblies:

Workload Before mean After mean Before allocated After allocated
Small method bodies, no fixtures 419.9 ms 272.1 ms 168.23 MB 129.74 MB
20 additional calls per method, no fixtures 2,448.5 ms 1,383.2 ms 1,055.14 MB 723.07 MB
Larger bodies with undisposed fixtures (control) 2,620.8 ms 2,134.4 ms 1,014.01 MB 1,019.71 MB

The larger-body workload allocated approximately 31% less. Timing is indicative: the unchanged fixture path also moved substantially between processes, and an earlier incomplete run did not show a small-body speedup. The full report below includes confidence intervals. No whole-build or test-execution speedup is claimed.

The benchmark includes Roslyn driver costs, excludes parsing and initial compilation validation, and runs one complete suite per iteration with analyzer concurrency disabled. Environment: Windows 11, i7-12700K, .NET 10.0.12, SDK 11.0.100-preview.7.26381.103; 10 measured iterations, 5 warmups, 1 launch.

Validation: all 47 DisposableFieldPropertyAnalyzerTests pass. Benchmark setup verifies identical diagnostics, including the same 100 TUnit0023 warnings for the fixture control. Analyzer builds succeed; the existing RS2007 release-header warning appears in both revisions. Reproduction source and full results are posted in the PR comment.

Summary by CodeRabbit

  • Bug Fixes
    • Improved analysis efficiency by skipping unnecessary teardown checks when no disposable fields or properties are present.
    • Prevented irrelevant final diagnostics from being generated in cases where there is nothing to analyze.

@thomhurst
thomhurst deployed to Pull Requests September 11, 2026 14:55 — with GitHub Actions Active
@thomhurst
thomhurst deployed to Pull Requests September 11, 2026 14:55 — with GitHub Actions Active
@thomhurst
thomhurst deployed to Pull Requests September 11, 2026 14:55 — with GitHub Actions Active
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T14:56:36.233660Z 6044af1 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: ea22845d-5b7a-4d28-92b3-1be228b6992f

📥 Commits

Reviewing files that changed from the base of the PR and between b43feca and 6044af1.

📒 Files selected for processing (1)
  • src/TUnit.Analyzers/DisposableFieldPropertyAnalyzer.cs

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The analyzer now exits CheckMethods when setup finds no disposable fields or properties. It skips teardown scanning and final diagnostic processing in that case.

Changes

Disposable member analysis

Layer / File(s) Summary
Guard teardown analysis
src/TUnit.Analyzers/DisposableFieldPropertyAnalyzer.cs
CheckMethods returns when createdObjects is empty. Teardown analysis and subsequent diagnostics are skipped.

Priority: ⬇️ Low

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Refactor

Merge Risk: ⚪ Minimal · up to 6044a

The optimization preserves analyzer behavior while avoiding unnecessary teardown scans for classes without disposable members.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main performance optimization: skipping teardown analysis when no disposable members require cleanup.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/disposable-empty-scan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checked the fields at dawn
No disposable leaves were drawn
The teardown path stayed still
No diagnostic had a bill
Cleanly skipped, as was the plan

Comment @coderabbitai help to get the list of available commands.

@thomhurst

Copy link
Copy Markdown
Owner Author

Benchmark reproduction and validation evidence

This compares the actual TUnit.Analyzers.dll built from baseline b43fecac6d with the DLL built from this PR. Each assembly is loaded into its own AssemblyLoadContext; both use the same Roslyn 4.14.0 host. The benchmark prints the module IDs to verify that the assemblies differ:

  • Before: 5c9bcc12-d107-4d32-a76a-50a0b093ad21
  • After: f8e72420-fc76-4234-a6c5-1eb90a461728

The workload has 10,000 methods across 100 classes. Bare uses a small arithmetic check. BodyHeavy adds 20 Math.Abs calls per method. UndisposedFixture adds an explicitly constructed MemoryStream field to each class and retains the larger bodies. Setup checks that both versions emit no diagnostics in the first two cases and the same 100 TUnit0023 diagnostics in the fixture control.

Each operation creates a fresh analyzer driver over the same prebuilt compilation, runs only DisposableFieldPropertyAnalyzer, and returns its diagnostics. Parsing, compilation validation, and initial warmup are outside measurement. Analyzer concurrency is disabled for the comparison. Roslyn driver costs are included. This does not measure a complete build or test run.

Reproduction:

  1. Build src/TUnit.Analyzers/TUnit.Analyzers.csproj -c Release at the baseline and PR revisions. Copy the resulting netstandard2.0/TUnit.Analyzers.dll files to before/ and after/ inside a standalone benchmark directory.
  2. Copy a locally built TUnit.Core.dll into that directory. This run used the 99.99.99.0 Core assembly from the preceding source-generator experiment. Use the project and program below.
  3. Validate with dotnet run -c Release -- --filter '*' --job Dry.
  4. Measure with dotnet run -c Release --no-build -- --filter '*' --iterationCount 10 --warmupCount 5 --launchCount 1 --invocationCount 1 --unrollFactor 1 --exporters json. Each invocation analyzes the entire 10,000-test suite; one invocation per iteration avoids unnecessary multi-second batches.

Regression validation: 47 tests passed, zero failures/skips, with:

dotnet build src/TUnit.Core/TUnit.Core.csproj -c Release -f netstandard2.0
dotnet build src/TUnit.Assertions/TUnit.Assertions.csproj -c Release -f netstandard2.0
dotnet build tests/TUnit.TestProject.Library/TUnit.TestProject.Library.csproj -c Release -f netstandard2.0
dotnet run --project tests/TUnit.Analyzers.Tests -c Release -f net10.0 -- --treenode-filter '/*/*/DisposableFieldPropertyAnalyzerTests/*'

The reference-assembly builds are required by the analyzer test project. The analyzer itself builds successfully; the existing RS2007 release-header warning occurs in both revisions.

Full BenchmarkDotNet report:


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9168/25H2/2025Update/HudsonValley2)
12th Gen Intel Core i7-12700K 3.60GHz, 1 CPU, 20 logical and 12 physical cores
.NET SDK 11.0.100-preview.7.26381.103
  [Host]     : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v3
  Job-JKJRQU : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v3

InvocationCount=1  IterationCount=10  LaunchCount=1  
UnrollFactor=1  WarmupCount=5  

Method Scenario Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
Before Bare 419.9 ms 28.66 ms 18.95 ms 1.00 0.06 13000.0000 7000.0000 - 168.23 MB 1.00
After Bare 272.1 ms 44.14 ms 29.20 ms 0.65 0.07 11000.0000 8000.0000 1000.0000 129.74 MB 0.77
Before BodyHeavy 2,448.5 ms 253.62 ms 167.76 ms 1.00 0.09 89000.0000 81000.0000 2000.0000 1055.14 MB 1.00
After BodyHeavy 1,383.2 ms 121.11 ms 80.11 ms 0.57 0.05 63000.0000 59000.0000 2000.0000 723.07 MB 0.69
Before UndisposedFixture 2,620.8 ms 455.53 ms 301.31 ms 1.01 0.16 86000.0000 78000.0000 2000.0000 1014.01 MB 1.00
After UndisposedFixture 2,134.4 ms 135.83 ms 89.84 ms 0.82 0.09 86000.0000 78000.0000 2000.0000 1019.71 MB 1.01

Project file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
    <Reference Include="TUnit.Core"><HintPath>TUnit.Core.dll</HintPath></Reference>
    <None Update="before/*.dll;after/*.dll" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>
</Project>

Program.cs:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Runtime.Loader;
using System.Text;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

[MemoryDiagnoser]
public class DisposableAnalyzerBenchmarks
{
    [Params("Bare", "BodyHeavy", "UndisposedFixture")]
    public string Scenario { get; set; } = "Bare";
    private CSharpCompilation _compilation = null!;
    private ImmutableArray<DiagnosticAnalyzer> _before;
    private ImmutableArray<DiagnosticAnalyzer> _after;
    private readonly CompilationWithAnalyzersOptions _options = new(
        new AnalyzerOptions(ImmutableArray<AdditionalText>.Empty), null,
        concurrentAnalysis: false, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: false);

    [GlobalSetup]
    public async Task Setup()
    {
        var trees = new List<SyntaxTree>();
        for (var c = 0; c < 100; c++)
        {
            var source = new StringBuilder("using System; using TUnit.Core; public class Tests" + c + " {");
            if (Scenario == "UndisposedFixture")
                source.Append("private System.IO.MemoryStream stream = new System.IO.MemoryStream();");
            for (var m = 0; m < 100; m++)
            {
                source.Append("[Test] public void Test").Append(m).Append("() { int x = 42; if (x * x + 1 != 1765) throw new Exception();");
                if (Scenario != "Bare")
                    for (var i = 0; i < 20; i++) source.Append("x = Math.Abs(x) + 1;");
                source.Append('}');
            }
            source.Append('}');
            trees.Add(CSharpSyntaxTree.ParseText(source.ToString()));
        }
        var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!).Split(Path.PathSeparator)
            .Select(p => MetadataReference.CreateFromFile(p)).ToList();
        references.Add(MetadataReference.CreateFromFile(typeof(TUnit.Core.TestAttribute).Assembly.Location));
        _compilation = CSharpCompilation.Create("SyntheticSuite", trees, references,
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
        var errors = _compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToArray();
        if (errors.Length != 0) throw new InvalidOperationException(string.Join("\n", errors.Select(e => e.ToString())));
        _before = Load("before");
        _after = Load("after");
        var before = await Before();
        var after = await After();
        var expectedCount = Scenario == "UndisposedFixture" ? 100 : 0;
        if (before.Length != expectedCount || after.Length != expectedCount ||
            before.Concat(after).Any(d => d.Id != "TUnit0023") ||
            !before.Select(d => d.ToString()).Order().SequenceEqual(after.Select(d => d.ToString()).Order()))
            throw new InvalidOperationException("Unexpected diagnostics: " + string.Join("\n", before.Concat(after)));
    }

    private static ImmutableArray<DiagnosticAnalyzer> Load(string name)
    {
        var path = Path.Combine(AppContext.BaseDirectory, name, "TUnit.Analyzers.dll");
        var assembly = new AssemblyLoadContext(name).LoadFromAssemblyPath(path);
        Console.WriteLine($"{name} analyzer MVID: {assembly.ManifestModule.ModuleVersionId}");
        return ImmutableArray.Create((DiagnosticAnalyzer)Activator.CreateInstance(
            assembly.GetType("TUnit.Analyzers.DisposableFieldPropertyAnalyzer")!)!);
    }

    [Benchmark(Baseline = true)]
    public Task<ImmutableArray<Diagnostic>> Before() => _compilation.WithAnalyzers(_before, _options).GetAnalyzerDiagnosticsAsync();

    [Benchmark]
    public Task<ImmutableArray<Diagnostic>> After() => _compilation.WithAnalyzers(_after, _options).GetAnalyzerDiagnosticsAsync();
}

@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR improves analyzer performance by skipping teardown invocation analysis when field, property, constructor, and setup analysis found no disposable members.

  • Preserves teardown analysis for classes with tracked disposable members.
  • Keeps static and instance analysis isolated through their existing independent collections.
  • Avoids an otherwise no-op reporting path when no members are tracked.

Confidence Score: 5/5

The PR appears safe to merge because the skipped teardown and reporting paths cannot affect diagnostics when the tracked-member collection is empty.

Teardown processing can only remove existing tracked members, while diagnostics are emitted only for members left in that collection; the new empty-collection return therefore preserves behavior.

Important Files Changed

Filename Overview
src/TUnit.Analyzers/DisposableFieldPropertyAnalyzer.cs Adds a behavior-preserving early return that avoids unnecessary teardown analysis when there are no tracked disposable members.

Reviews (1): Last reviewed commit: "perf: skip disposal teardown scans witho..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Review: perf: skip teardown analysis when no disposable members need cleanup (#6780)

Verdict: Looks good.

What the change does

Adds an early return in DisposableFieldPropertyAnalyzer.CheckMethods (src/TUnit.Analyzers/DisposableFieldPropertyAnalyzer.cs:64-67): if createdObjects is empty after field-initializer and setup analysis, the method skips the teardown scan and diagnostic-reporting loop entirely.

Correctness

I traced the data flow to confirm this is safe:

  • CheckTeardowns (line 227) only ever calls createdObjects.TryRemove(...) — it never adds entries. It removes an entry only when a matching created member exists at the same HookLevel.
  • The only ReportDiagnostic call in the file (line 73) iterates createdObjects after both loops finish.
  • Therefore, if createdObjects is empty before teardown analysis runs, teardown analysis can only leave it empty, and the reporting loop would have produced no diagnostics anyway. The early return changes nothing observable — it's a pure no-op elimination, not a behavior change.
  • ConcurrentDictionary<TKey,TValue>.IsEmpty is a legitimate O(1)-ish thread-safe check, appropriate here even though createdObjects is actually local/non-shared within a single CheckMethods call.

Scope and validation

  • Change is tightly scoped (6 lines, one file), matches the stated goal, and doesn't touch the source-generator/engine dual-implementation surface, so the CLAUDE.md requirement to keep TUnit.Core.SourceGenerator/TUnit.Engine behavior in sync doesn't apply here (this is analyzer-only, not metadata collection).
  • PR description includes credible benchmark numbers (BenchmarkDotNet, allocation deltas, diagnostic-parity verification) and states all 47 existing DisposableFieldPropertyAnalyzerTests pass — appropriate validation for a perf-only change with no behavior delta.
  • No new tests were added, which is reasonable here since the change is provably a no-op with respect to diagnostics (verified above), not new logic requiring new coverage.

Suggestions

None — this is a clean, well-justified micro-optimization with a clear invariant (teardown analysis is subtractive-only) backing its safety. No architectural or design concerns; nothing to flag on maintainability or scalability grounds for a change this small and self-contained.

@thomhurst
thomhurst enabled auto-merge (squash) September 11, 2026 14:58
@thomhurst
thomhurst merged commit e322d68 into main Sep 11, 2026
14 checks passed
@thomhurst
thomhurst deleted the perf/disposable-empty-scan branch September 11, 2026 15:23
github-actions Bot pushed a commit to IntelliTect/CodingGuidelines that referenced this pull request Sep 14, 2026
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.66.27 to
1.67.0.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit.Core's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.67.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.67.0 -->

## What's Changed
### Other Changes
* docs: clarified and updated attributes comparison for xUnit 3 by
@​304NotModified in thomhurst/TUnit#6774
* perf: read inline argument metadata without reflection by @​thomhurst
in thomhurst/TUnit#6778
* perf: limit converter discovery to declarations by @​thomhurst in
thomhurst/TUnit#6779
* perf: skip teardown analysis when no disposable members need cleanup
by @​thomhurst in thomhurst/TUnit#6780
* perf: avoid line allocations when writing generated source by
@​thomhurst in thomhurst/TUnit#6781
* perf: avoid formatting interface names for data-source checks by
@​thomhurst in thomhurst/TUnit#6782
* perf: skip unannotated property data-source candidates by @​thomhurst
in thomhurst/TUnit#6784
* fix: fold inner exceptions into IDE test failure output by @​thomhurst
in thomhurst/TUnit#6777
* perf: reuse argument-free attribute initializer text by @​thomhurst in
thomhurst/TUnit#6788
* perf: extract test metadata in attribute transforms by @​thomhurst in
thomhurst/TUnit#6789
* perf: skip receiver registration for ordinary objects by @​thomhurst
in thomhurst/TUnit#6790
* perf: cache reporting properties on test contexts by @​thomhurst in
thomhurst/TUnit#6791
* fix: preserve executor registration, limiter precedence, and timeout
classification by @​Nice3point in
thomhurst/TUnit#6768
### Dependencies
* chore(deps): update tunit to 1.66.27 by @​thomhurst in
thomhurst/TUnit#6742
* chore(deps): update dependency bunit to 2.10.3 by @​thomhurst in
thomhurst/TUnit#6745
* chore(deps): update dependency imposter to 0.1.11 by @​thomhurst in
thomhurst/TUnit#6744
* chore(deps): update dependency microsoft.kiota.abstractions to 2.1.2
by @​thomhurst in thomhurst/TUnit#6747
* chore(deps): update dependency microsoft.templateengine.authoring.cli
to v10.0.401 by @​thomhurst in
thomhurst/TUnit#6750
* chore(deps): update dependency fsharp.core to 10.1.401 by @​thomhurst
in thomhurst/TUnit#6748
* chore(deps): update dependency
microsoft.templateengine.authoring.templateverifier to 10.0.401 by
@​thomhurst in thomhurst/TUnit#6751
* chore(deps): update dependency system.commandline to 2.0.12 by
@​thomhurst in thomhurst/TUnit#6752
* chore(deps): update dependency dotnet-sdk to v10.0.401 by @​thomhurst
in thomhurst/TUnit#6754
* chore(deps): update microsoft.extensions to 10.0.12 by @​thomhurst in
thomhurst/TUnit#6755
* chore(deps): update microsoft.aspnetcore to 10.0.12 by @​thomhurst in
thomhurst/TUnit#6753
* chore(deps): update dependency microsoft.entityframeworkcore to
10.0.12 by @​thomhurst in thomhurst/TUnit#6749
* chore(deps): update mcr.microsoft.com/dotnet/sdk docker tag to v11 by
@​thomhurst in thomhurst/TUnit#6756
* chore(deps): update dependency microsoft.net.test.sdk to 18.10.0 by
@​thomhurst in thomhurst/TUnit#6761
* chore(deps): update microsoft.extensions to 10.10.0 by @​thomhurst in
thomhurst/TUnit#6762
* chore(deps): update react to ^19.3.0 by @​thomhurst in
thomhurst/TUnit#6763
* chore(deps): update dependency awssdk.sqs to 4.0.100.13 by @​thomhurst
in thomhurst/TUnit#6764
* chore(deps): update dependency polyfill to 11.3.0 by @​thomhurst in
thomhurst/TUnit#6765
* chore(deps): update dependency polyfill to 11.3.0 by @​thomhurst in
thomhurst/TUnit#6766
* chore(deps): update dependency stackexchange.redis to 3.2.0 by
@​thomhurst in thomhurst/TUnit#6769
* chore(deps): update dependency microsoft.net.stringtools to 18.10.1 by
@​thomhurst in thomhurst/TUnit#6771
* chore(deps): update dependency dotnet-trace to v10.0.745401 by
@​thomhurst in thomhurst/TUnit#6773
* chore(deps): bump colord from 2.9.3 to 2.10.0 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6759
* chore(deps): bump joi from 17.13.4 to 17.13.7 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6758
* chore(deps): bump js-yaml from 4.3.1 to 4.3.2 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6757
* chore(deps): update dependency yaml to v2.9.1 by @​thomhurst in
thomhurst/TUnit#6785
* chore(deps): update verify to 32.0.1 by @​thomhurst in
thomhurst/TUnit#6786
* chore(deps): update dependency nunit.analyzers to 4.15.0 by
@​thomhurst in thomhurst/TUnit#6792

## New Contributors
* @​304NotModified made their first contribution in
thomhurst/TUnit#6774
* @​Nice3point made their first contribution in
thomhurst/TUnit#6768
 ... (truncated)

Commits viewable in [compare
view](thomhurst/TUnit@v1.66.27...v1.67.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit.Core&package-manager=nuget&previous-version=1.66.27&new-version=1.67.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This was referenced Sep 14, 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.

1 participant