Skip to content

[leak-fix] Fix GradientBrush.GradientStops memory leak (Fixes #36363) - #36521

Closed
github-actions[bot] wants to merge 31 commits into
mainfrom
leak-fix/issue-36363-22ae0f411a43e2b9
Closed

[leak-fix] Fix GradientBrush.GradientStops memory leak (Fixes #36363)#36521
github-actions[bot] wants to merge 31 commits into
mainfrom
leak-fix/issue-36363-22ae0f411a43e2b9

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Note

🔍 AI-generated PR. This fix and its regression tests were produced by the Memory Leak Fixer workflow. Please review carefully before merging.

Fixes #36363

The leak

GradientBrush.UpdateGradientStops subscribed the brush directly to GradientStopCollection.CollectionChanged and to PropertyChanged on every contained GradientStop. A shared or long-lived non-empty collection could therefore retain transient brushes through both the collection-level and per-stop event handlers.

The fix

Route the collection subscription through WeakNotifyCollectionChangedProxy and each stop subscription through WeakNotifyPropertyChangedProxy. A private GradientStopSubscriptions helper owns the collection proxy and per-stop proxies while the brush is alive. It removes subscriptions when collections or stops change and handles reset/reentrant replacement safely. Live collection replacement uses an owner-aware cleanup path that also clears unused parents, while a separately named finalizer path clears collected weak-parent references.

Collected-parent cleanup now keeps ordinary inherited context and the source used by a BindingContextProperty binding in element-owned weak references. BindingBase.Context unwraps the weak holder for normal binding application, and MultiBinding propagates the source to its proxy as inherited context rather than a strong local value. A finalizer therefore only atomically transitions element-owned fields; it never reads BindablePropertyContext, binding collections, or mutable BindingBase state. This also releases a binding-held inherited source when no dispatcher exists, without requiring later application access.

Objects created before a dispatcher exists now cache an available dispatcher when either a handler or parent is attached on a normal thread. Parent capture occurs while the hierarchy is still available; finalizer cleanup therefore reads only cached state and never resolves services from the finalizer thread. Later normal access uses a non-throwing dispatcher lookup that does not traverse the already-cleared parent hierarchy. The public Dispatcher getter uses the same volatile-read and compare-exchange winner semantics, so concurrent background discovery cannot overwrite a dispatcher captured during handler or parent attachment. If a cached dispatcher has already been disposed, both finalizer and normal-access cleanup catch ObjectDisposedException from IsDispatchRequired or Dispatch, reset single-flight scheduling when needed, and leave cleanup pending for later safe access. Parent and BindingContext reads therefore return safely instead of leaking dispatcher disposal.

Each cleanup uses a unique pending-generation token. Dispatcher callbacks capture that exact generation, so a stale callback cannot clear a newer cleanup after reparenting. An atomic per-token single-flight flag allows only one accepted dispatcher callback to be queued; a rejected dispatch resets the flag so a later access can retry. Later dispatcher-safe Parent, RealParent, or BindingContext access drains the pending work without flooding the dispatcher with duplicate no-op callbacks.

Binding replacement and removal preserve or atomically claim the pending generation. Replacement bindings apply against a null inherited source while cleanup is pending; final removal clears the removed binding's stored specificity value so a manual override cannot later reveal stale data. Forced cleanup refreshes a hidden lower-specificity binding while preserving an effective manual BindingContext.

The handler delegates remain strongly cached by the live brush so weak forwarding continues after GC. Parent cleanup is owner- and occurrence-aware: a brush clears a stop's parent only when that brush is still the current parent and the same stop instance no longer appears in its current collection; this check uses reference identity rather than GradientStop value equality. Clearing, removing, or replacing stops from an older sharing brush therefore cannot detach a stop currently owned by another live brush, and duplicate occurrences retain their parent until the last occurrence is removed.

The public GradientBrush type remains finalizer-free and there are no public API additions. Binding-context propagation also returns safely when GradientStops is null or contains null entries.

Regression tests

GradientBrushMemoryTests covers:

  • GradientBrushDoesNotLeakWhenSharingGradientStops — a shared non-empty collection does not retain a transient brush.
  • CollectedBrushDoesNotLeaveStaleGradientStopParent — a collected brush leaves no stale weak parent or misleading RealParent warning on a surviving stop.
  • CollectedBrushClearsGradientStopInheritedBindingContext — collected-parent cleanup clears the inherited context, resets a bound stop property, and prevents later source changes from reaching it.
  • CollectedBrushClearsGradientStopBindingContextPropertyBindingSource — cleanup clears inherited context held by a binding on BindingContextProperty, raises the normal context transition, and detaches the stale source.
  • CollectedBrushWithoutDispatcherReleasesBindingContextPropertyBindingSource — a binding-held source is collectible without a dispatcher and without reading Parent or BindingContext; after the weak source is collected, the test proves the stale 0.25f stored binding value still requires normal cleanup.
  • CollectedBrushWithoutDispatcherReleasesMultiBindingContextPropertyBindingSourceMultiBinding proxy state also remains weak and collectible without a dispatcher.
  • CollectedBrushDispatchesGradientStopInheritedBindingContextCleanup — finalizer cleanup queues binding work and does not raise BindingContextChanged until the dispatched action executes.
  • CollectedBrushUsesDispatcherAttachedAfterGradientStopCreation — an element created without a dispatcher captures one from a later handler attachment and queues finalizer cleanup through it; the regression times out on the prior head and passes with the fix.
  • CollectedBrushFinalizerDoesNotResolveLateHandlerDispatcher — finalizer cleanup never reads a late handler service provider; later normal BindingContext access may resolve and cache that dispatcher before safely queueing cleanup.
  • CollectedBrushUsesDispatcherAvailableWhenParentIsAssigned — a child created without a dispatcher captures the dispatcher available through its new parent before that weak parent can be collected; the regression times out on the previous head and passes with the fix.
  • CollectedInheritedContextStillClearsAppliedBindingValues — when both an ordinary inherited source and its parent are collected before finalizer cleanup, stale bound target values remain pending until normal access safely clears them; the regression verifies the reset and single context notification.
  • BindingContextAccessBeforeDispatchedCleanupDoesNotRunCallbacks — a background BindingContext read before queued cleanup executes does not run bindings or callbacks off-dispatcher.
  • BoundBindingContextReturnsNullWhileInheritedCleanupIsPending — a collected public Parent drives generic cleanup without fix-only test APIs; rejected dispatch remains retryable, accepted dispatch is single-flight across repeated BindingContext reads, and stale bound values remain hidden.
  • CleanupLeavesPendingWhenDispatcherIsDisposedObjectDisposedException from either IsDispatchRequired or Dispatch is contained for both finalizer cleanup and normal Parent/BindingContext access; cleanup remains pending and completes after the dispatcher becomes usable.
  • FailedDispatchLeavesGradientStopInheritedBindingContextCleanupPending — dispatch rejection leaves cleanup pending; a later dispatcher-safe access completes it.
  • ParentAccessBeforeSubscriptionFinalizerClearsInheritedBindingContext — reading Parent before the subscription helper finalizes still clears the inherited context and binding source.
  • ParentAccessAfterCollectedBrushClearsGradientStopInheritedBindingContext — reading Parent after finalization has already cleared the weak parent drains pending inherited-context cleanup.
  • CollectingPreviousBrushPreservesCurrentGradientStopParent — collecting a previously owning brush after reparenting preserves the current live brush parent and binding context.
  • CollectingPreviousBrushPreservesCurrentGradientStopBindingContextPropertyBindingSource — the binding-held source also follows the current brush after reparenting.
  • ClearingManualBindingContextAfterCollectedBrushDoesNotRestoreStaleBindingValue — forced cleanup refreshes the hidden binding value without replacing a manual override.
  • ReplacingBindingContextBindingWhileCleanupIsPendingUsesNullInheritedSource — binding replacement cannot unwrap or revive the old inherited source.
  • RemovingBindingContextBindingWhileCleanupIsPendingClearsStoredBindingValue — final binding removal cannot leave stale data behind a manual value.
  • StaleDispatchedCleanupDoesNotClearNewPendingBindingContextCleanup — a callback from an older cleanup generation cannot consume a newer pending cleanup.
  • GradientStopChangesStillInvalidateAfterGc — a live brush keeps receiving stop invalidation after GC.
  • SharedGradientStopsInvalidateEachLiveBrushAfterGc — multiple live brushes sharing one collection each invalidate.
  • AliveBrushStillInvalidatesAfterSiblingBrushIsCollected — one sharing brush can be collected without suppressing invalidation for its live sibling.
  • RemovingAndReplacingGradientStopsMovesSubscriptions — removed/old stops detach and replacement stops attach.
  • ReplacingGradientStopsInvalidatesBrush — replacing non-empty stops with null or an empty collection raises one explicit invalidation.
  • NullGradientStopCollectionAllowsBindingContextChange — a later binding-context change remains safe while GradientStops is null.
  • NullGradientStopEntryAllowsBindingContextChange — null entries are ignored during inherited-context propagation.
  • SharedGradientStopsPreserveExistingMostRecentlyAssignedParentBehavior — shared collections retain the existing single-parent/inherited-context behavior.
  • DetachingSharedStopFromPreviousBrushPreservesCurrentParent — clear, remove, and collection replacement from an older brush preserve the current brush parent and binding context.
  • DuplicateGradientStopsPreserveOccurrenceSubscriptions — duplicate occurrences retain one invalidation subscription per occurrence and keep their parent until the final occurrence is removed.
  • DetachingValueEqualStopClearsRemovedParent — removal, item replacement, and collection replacement clear only the detached instance when an equal-valued stop remains.
  • RemovingGradientStopAllowsReentrantReuse — reentrant reuse of a removed stop preserves the newly installed property-change subscription.
  • ClearingGradientStopsAllowsReentrantReplacement — reset cleanup permits reentrant collection replacement and verifies that clear and replacement each invalidate once.
  • ClearingAndReusingGradientStopsKeepsNewStopsSubscribed — later additions remain subscribed after clear/reuse.

DispatcherExtensionsTest.DispatcherGetterPreservesDispatcherCapturedDuringHandlerAttachment deterministically verifies that a concurrent background lookup cannot overwrite the dispatcher captured during handler attachment.

Known existing behavior: a GradientStop is an Element with one Parent and one inherited binding context. When a stop is shared by multiple brushes, the most recently assigned brush remains the current parent. This PR preserves that single-parent model while ensuring cleanup by a non-owning brush does not erase the current owner.

All 40 focused GradientBrushMemoryTests pass, and the focused cleanup/dispatcher filter passes 143 tests. The latest complete Controls.Core.UnitTests run before this test-only hardening passed 5,706 tests with 30 existing tests skipped. The original review gate verified the leak regression red without the fix and green with it, and the Brush UI category completed successfully. The revised generic regression was also run against merge base 0395a53b with all production fix files absent: it compiled and failed behaviorally on the stale FooBar value, while the fixed head passes.

Scope

Managed cross-platform changes in GradientBrush.cs, Element.cs, BindableObject.cs, DispatcherExtensions.cs, BindingBase.cs, and MultiBinding.cs, with focused unit coverage in GradientBrushMemoryTests.cs, BindableObjectUnitTests.cs, and DispatcherExtensionsTests.cs, plus failed-dispatch support in DispatcherStub.cs. No existing test was muted, skipped, or weakened.

Generated by Memory Leak Fixer

GradientBrush subscribed to the shared GradientStopCollection's
CollectionChanged with a strong instance delegate, so a long-lived/shared
GradientStopCollection rooted every transient brush assigned to it. Route
the subscription through WeakNotifyCollectionChangedProxy (the same weak
pattern already used by Border for StrokeDashArray) and add a finalizer that
unsubscribes the proxy.

Adds a regression test that fails without the fix and passes with it.

Generated by the Memory Leak Fixer agentic workflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added agentic-workflows perf/memory-leak 💦 Memory usage grows / objects live forever (sub: perf) labels Jul 11, 2026
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 11, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

Comment thread src/Controls/src/Core/GradientBrush.cs Outdated
@MauiBot MauiBot added s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Jul 11, 2026
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 11, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bbcdaa9d-d75a-4e3b-b375-cf827a689644
@github-actions

Copy link
Copy Markdown
Contributor Author

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36521

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36521"

@kubaflo

kubaflo commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated update (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot Addressed the non-empty GradientStops leak finding in 16141ad53ff: collection and per-stop subscriptions are weak, cleanup is privately owned, and regression coverage now includes post-GC notifications and reset reentrancy. Ready for re-review.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 11, 2026
@kubaflo

This comment has been minimized.

MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 11, 2026
@kubaflo

kubaflo commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated update (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot Updated the PR description to match 16141ad53ff: it now documents both weak subscription paths, the private helper finalizer, all three regression tests, and the unchanged public API surface. The [leak-fix] title is retained intentionally for workflow grouping and already names GradientBrush.GradientStops explicitly. Ready for re-review.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 11, 2026
MauiBot

This comment was marked as outdated.

@kubaflo

kubaflo commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated response (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot The latest review found no current code issues. The live description already documents both weak subscription paths, the private GradientStopSubscriptions finalizer, all three regression tests, and no public API change at 16141ad53ff. No no-op commit or duplicate trigger was posted.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 12, 2026
@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated follow-up (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot Review #4704706363 reports two warnings that are already fixed in the reviewed head 79818b1ada46216bfdc34168f905b001d4a1ea63:

  • BindableObject.Dispatcher now reads with Volatile.Read and stores through Interlocked.CompareExchange (GetDispatcherIfAvailable / SetDispatcherIfUnset). DispatcherGetterPreservesDispatcherCapturedDuringHandlerAttachment deterministically covers the getter/attachment race.
  • MarkInheritedBindingContextForCleanup no longer has the dead _inheritedContext.Target fast-clear path; it always installs PendingInheritedBindingContextCleanup, allowing binding reapplication to clear stale values. BoundBindingContextReturnsNullWhileInheritedCleanupIsPending and the focused GradientBrush cleanup tests cover this behavior.

The report itself ranks these two refinements as the winning pr-plus-reviewer candidate, but that candidate is already the current PR head. No additional code change or no-op commit was needed. The separate _inheritedBindingContext early-clear thread remains intentionally unresolved because its stored bound value still requires cleanup.

@kubaflo

This comment has been minimized.

MauiBot

This comment was marked as outdated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bbcdaa9d-d75a-4e3b-b375-cf827a689644
@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot Review #4705401238 repeats the earlier proposal to discover a dispatcher from the CLR finalizer path. That change was intentionally not applied because TryFindDispatcher(includeParents: false) reads handler and application service providers, which may already be disposed during teardown. Handler and parent attachment cache dispatchers on normal threads; later normal Parent, RealParent, or BindingContext access may safely perform late discovery.

Commit e91b117 adds CollectedBrushFinalizerDoesNotResolveLateHandlerDispatcher and makes the safety boundary explicit in BindableObject. The regression proves finalization performs zero late handler service resolutions, then normal BindingContext access resolves the newly available dispatcher and queues cleanup. Removing the normal-access gate to match the suggested candidate makes the test fail with expected resolution count 0, actual 1; the restored safe implementation passes, along with all 48 focused cleanup/dispatcher tests.

The PR description now documents this regression. The separate inherited-BindingContext early-clear disagreement remains intentionally unresolved.

@kubaflo

This comment has been minimized.

MauiBot

This comment was marked as outdated.

@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated follow-up (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot Review #4705732523 found zero current errors, warnings, or suggestions on e91b1174690c6d7a4cfa5f8a327b35631d789d1d and selected the submitted PR as the only passing complete fix. The finalizer-safety regression confirms cleanup uses cached dispatcher state during finalization and defers service discovery to normal access.

No additional code or no-op commit is needed. The separate _inheritedBindingContext early-clear disagreement remains intentionally unresolved for the documented stale stored-value reason, and no redundant review rerun was requested.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

Comment thread src/Controls/src/Core/BindableObject.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bbcdaa9d-d75a-4e3b-b375-cf827a689644
@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated update (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot The critical disposed-dispatcher finding is addressed in 6685ddf. Finalizer-reachable cleanup now contains ObjectDisposedException without running callbacks or losing retry state, while normal access retains explicit error behavior. The two-case regression failed on the previous head and now passes; all 143 focused GradientBrush, BindableObject, and dispatcher tests pass. The inline thread is resolved and the PR description is current.

@kubaflo

This comment has been minimized.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

Comment thread src/Controls/src/Core/BindableObject.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bbcdaa9d-d75a-4e3b-b375-cf827a689644
@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated update (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot, the normal-access disposed-dispatcher finding is fixed in 049ec12. Parent and BindingContext reads now safely retain pending cleanup instead of leaking ObjectDisposedException, with regression coverage for both dispatcher throw sites. Ready for re-review.

MauiBot

This comment was marked as outdated.

@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot Review 4707031431 ranks the current pr-plus-reviewer head first, reports 0 errors, and selects no alternative fix. The gate is inconclusive only because the without-fix checkout cannot compile a regression that intentionally calls a fix-only internal cleanup method; the with-fix suites pass. The generated narrative that normal access clears the cached dispatcher is not the implementation: 049ec12 safely leaves cleanup pending without mutating the cached dispatcher, as the current PR description and regression state. The two warnings are broad architectural caution rather than actionable defects, so no code change or redundant rerun is warranted. The [leak-fix] prefix remains intentional for this coordinated PR fleet.

MauiBot

This comment was marked as outdated.

@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo)

@MauiBot Review 4707379138 is clean on the current head 049ec123be7: its code-review section reports 0 errors, 0 warnings, and 0 suggestions, and the gate passed. The comparative report’s pr-plus-reviewer winner describes behavior already present in this head: DispatchInheritedBindingContextCleanup contains disposed-dispatcher failures at both IsDispatchRequired and Dispatch, preserves pending cleanup, and resets single-flight scheduling when dispatch is not accepted. The expanded disposed-dispatcher regression also covers finalizer and normal Parent/BindingContext access.

The report’s lower-ranked raw pr entry refers to the earlier run snapshot, not the submitted current head. No additional code change or redundant rerun is warranted. The [leak-fix] prefix remains intentional for this coordinated PR fleet.

@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@github-actions[bot] — new AI review results are available based on this last commit: 6685ddf. To request a fresh review after new comments or commits, comment /review rerun.

Gate Inconclusive Confidence Medium Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ INCONCLUSIVE

Platform: ANDROID · Base: main · Merge base: a96cb62b

🩺 Base branch does not compile — the without-fix build failed. The gate's "does the test fail without the fix" check is unreliable here; this usually means main is broken or a merge-base file went missing. Investigate before trusting this gate.

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs(836,46): error CS1061: 'MockBindable' does not contain a definition for 'ClearRealParentAndInheritedContextIfCollected'...

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 BindableObjectUnitTests BindableObjectUnitTests 🛠️ BUILD ERROR ✅ PASS — 84s
🧪 DispatcherExtensionsTest DispatcherExtensionsTest 🛠️ BUILD ERROR ✅ PASS — 17s
🧪 GradientBrushMemoryTests GradientBrushMemoryTests 🛠️ BUILD ERROR ✅ PASS — 20s
🔴 Without fix — 🧪 BindableObjectUnitTests: 🛠️ BUILD ERROR · 108s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs(836,46): error CS1061: 'MockBindable' does not contain a definition for 'ClearRealParentAndInheritedContextIfCollected' and no accessible extension method 'ClearRealParentAndInheritedContextIfCollected' accepting a first argument of type 'MockBindable' could be found (are you missing a using directive or an assembly reference?) [/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 BindableObjectUnitTests: PASS ✅ · 84s

(no coded error found; showing last 1200 chars)

ed TestBindingOneWayOnReadOnly [< 1 ms]
  Passed PropertyChangingSameValue [< 1 ms]
  Passed BindingsAppliedUnappliedWithNullContext [< 1 ms]
  Passed DefaultValueCreatorIsInvokedOnlyAtFirstTime [< 1 ms]
  Passed PropertyChanging [< 1 ms]
  Passed RemoveBindingInvalid [< 1 ms]
  Passed BindingIsPreservedOnStyleBinding [< 1 ms]
  Passed ClearValueDoesNotTriggersINPCOnSameValues [< 1 ms]
  Passed PropertyChangedSameValue [< 1 ms]
  Passed DoesNotRaiseOnSilent [< 1 ms]
  Passed StyleBindingIsOverridenByStyleValue [< 1 ms]
  Passed ValueIsPreservedOnStyleBinding [< 1 ms]
  Passed PropertyChangingDefaultValue [< 1 ms]
  Passed BindingOnBindingContextDoesntReapplyBindingContextBinding [< 1 ms]
  Passed GetSetValue [< 1 ms]
[xUnit.net 00:00:01.70]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed BoundBindingContextReturnsNullWhileInheritedCleanupIsPending [21 ms]
  Passed IsSetIsTrueWhenPropSetByDefaultValueCreator [< 1 ms]
  Passed PropertyChanged [< 1 ms]
  Passed CoerceValue [< 1 ms]
  Passed StyleValueIsOverridenByStyleValue [< 1 ms]
  Passed BindingContextChangedCompareReferences [< 1 ms]

Test Run Successful.
Total tests: 96
     Passed: 96
 Total time: 2.2520 Seconds

🔴 Without fix — 🧪 DispatcherExtensionsTest: 🛠️ BUILD ERROR · 29s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs(836,46): error CS1061: 'MockBindable' does not contain a definition for 'ClearRealParentAndInheritedContextIfCollected' and no accessible extension method 'ClearRealParentAndInheritedContextIfCollected' accepting a first argument of type 'MockBindable' could be found (are you missing a using directive or an assembly reference?) [/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 DispatcherExtensionsTest: PASS ✅ · 17s

(no coded error found; showing last 1200 chars)

n 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.30]   Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:03.57]   Discovered:  Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:03.58]   Starting:    Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:04.01]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed DispatchIfRequiredAsync_ShouldExecuteAction_WhenDispatchIsNotRequired [160 ms]
  Passed DispatchIfRequiredAsync_FuncTask_ShouldCallDispatch_WhenDispatchIsRequired [27 ms]
  Passed DispatchIfRequiredAsync_ShouldCallDispatchAsync_WhenDispatchIsRequired [1 ms]
  Passed DispatchIfRequiredAsync_FuncTask_ShouldExecuteFunction_WhenDispatchIsNotRequired [5 ms]
  Passed DispatchIfRequired_ShouldExecuteAction_WhenDispatchIsNotRequired [3 ms]
  Passed DispatchIfRequired_ShouldCallDispatch_WhenDispatchIsRequired [< 1 ms]
  Passed DispatcherGetterPreservesDispatcherCapturedDuringHandlerAttachment [111 ms]

Test Run Successful.
Total tests: 7
     Passed: 7
 Total time: 5.2151 Seconds

🔴 Without fix — 🧪 GradientBrushMemoryTests: 🛠️ BUILD ERROR · 29s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs(836,46): error CS1061: 'MockBindable' does not contain a definition for 'ClearRealParentAndInheritedContextIfCollected' and no accessible extension method 'ClearRealParentAndInheritedContextIfCollected' accepting a first argument of type 'MockBindable' could be found (are you missing a using directive or an assembly reference?) [/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 GradientBrushMemoryTests: PASS ✅ · 20s

(no coded error found; showing last 1200 chars)

ed CollectedBrushDoesNotLeaveStaleGradientStopParent [224 ms]
  Passed CollectedBrushUsesDispatcherAttachedAfterGradientStopCreation [431 ms]
  Passed FailedDispatchLeavesGradientStopInheritedBindingContextCleanupPending [96 ms]
  Passed CollectedBrushClearsGradientStopBindingContextPropertyBindingSource [95 ms]
  Passed DuplicateGradientStopsPreserveOccurrenceSubscriptions [< 1 ms]
  Passed NullGradientStopCollectionAllowsBindingContextChange [< 1 ms]
  Passed ParentAccessBeforeSubscriptionFinalizerClearsInheritedBindingContext [63 ms]
  Passed CollectedBrushUsesDispatcherAvailableWhenParentIsAssigned [105 ms]
  Passed ClearingManualBindingContextAfterCollectedBrushDoesNotRestoreStaleBindingValue [104 ms]
[xUnit.net 00:00:06.35]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed RemovingBindingContextBindingWhileCleanupIsPendingClearsStoredBindingValue [78 ms]
  Passed ClearingGradientStopsAllowsReentrantReplacement [2 ms]
  Passed CollectedBrushFinalizerDoesNotResolveLateHandlerDispatcher [69 ms]
  Passed ReplacingBindingContextBindingWhileCleanupIsPendingUsesNullInheritedSource [68 ms]

Test Run Successful.
Total tests: 40
     Passed: 40
 Total time: 7.6274 Seconds

⚠️ Failure Details

  • 🛠️ BindableObjectUnitTests without fix: build failed before tests could run
    • /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs(836,46): error CS1061: 'MockBindable' does not contain a definition for 'ClearRealParentAndInheritedContextIfCollected'...
  • 🛠️ DispatcherExtensionsTest without fix: build failed before tests could run
    • /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs(836,46): error CS1061: 'MockBindable' does not contain a definition for 'ClearRealParentAndInheritedContextIfCollected'...
  • 🛠️ GradientBrushMemoryTests without fix: build failed before tests could run
    • /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/BindableObjectUnitTests.cs(836,46): error CS1061: 'MockBindable' does not contain a definition for 'ClearRealParentAndInheritedContextIfCollected'...
📁 Fix files reverted (6 files)
  • src/Controls/src/Core/BindableObject.cs
  • src/Controls/src/Core/BindingBase.cs
  • src/Controls/src/Core/DispatcherExtensions.cs
  • src/Controls/src/Core/Element/Element.cs
  • src/Controls/src/Core/GradientBrush.cs
  • src/Controls/src/Core/MultiBinding.cs

📱 UI Tests — Brush

Detected UI test categories: Brush

Deep UI tests — 42 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Brush 42/42 ✓
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

📋 Pre-Flight — Context & Validation

Issue: #36363 - [leak-scan] GradientBrush.GradientStops — shared GradientStopCollection strongly roots the brush via CollectionChanged
PR: #36521 - [leak-fix] Fix GradientBrush.GradientStops memory leak (Fixes #36363)
Platforms Affected: All platforms; pure managed Controls core code. Requested test platform: android, but the changed code and regression tests are cross-platform unit-test coverage.
Files Changed: 6 implementation, 4 test

Key Findings

  • Issue #36363 reports that a long-lived/shared non-empty GradientStopCollection roots transient GradientBrush instances through strong CollectionChanged and per-stop PropertyChanged subscriptions, plus stop parent/context references.
  • Current PR fix uses weak collection/per-stop event proxies and adds owner-aware cleanup for stale GradientStop.Parent and inherited binding-context state.
  • The implementation also changes shared BindableObject, Element, BindingBase, MultiBinding, and dispatcher lookup behavior to preserve existing parent/binding semantics while allowing dead brush cleanup.
  • Prior MauiBot error-level review about per-stop PropertyChanged still leaking is addressed in the current diff.
  • GitHub CLI is unauthenticated in this environment, so PR checks could not be queried; public API/web access was used for issue/PR narrative and comments.

Code Review Summary

Verdict: NEEDS_DISCUSSION
Confidence: low
Errors: 0 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • No current high-confidence code findings from the expert reviewer.
  • Coverage gap for this run: required CI status is unavailable locally and user-provided gate was inconclusive; this does not imply failure.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #36521 Weak event proxies plus weak parent/inherited-context cleanup in shared Controls infrastructure ⚠️ INCONCLUSIVE (Gate) BindableObject.cs, BindingBase.cs, DispatcherExtensions.cs, Element.cs, GradientBrush.cs, MultiBinding.cs, tests Original PR; broad but preserves existing GradientStop.Parent behavior

🔬 Code Review — Deep Analysis

Code Review — PR #36521

Independent Assessment

What this changes: The PR fixes a managed memory leak in GradientBrush.GradientStops by replacing strong CollectionChanged and per-stop PropertyChanged subscriptions with weak event proxies, then broadens inherited BindingContext, parent, and dispatcher cleanup so a collected brush does not leave stale parent/context state on surviving GradientStop instances. It also adds extensive unit tests around shared gradient-stop collections, stale parent cleanup, binding-context cleanup, dispatcher scheduling, and live sibling invalidation.

Inferred motivation: A long-lived or shared GradientStopCollection can retain transient GradientBrush instances through strong event-handler targets and through each stop's parent/inherited-context graph.

Reconciliation with PR Narrative

Author claims: PR #36521 fixes issue #36363, where shared GradientStopCollection instances strongly root transient GradientBrush instances. The PR claims weak event proxy subscriptions, owner-aware cleanup, weak inherited-context storage, dispatcher-safe finalizer cleanup, and broad regression coverage.

Agreement/disagreement: The local diff matches the claim. The current PR fix is substantially broader than the original issue because it preserves existing GradientStop.Parent / inherited binding-context behavior while allowing dead brush cleanup without strong ownership cycles.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
Shared non-empty GradientStopCollection still roots the brush through per-stop PropertyChanged handlers; empty-collection test misses this leak. MauiBot inline review on GradientBrush.cs ✅ Fixed Current GradientBrush.GradientStopSubscriptions uses WeakNotifyPropertyChangedProxy per stop and tests use non-empty shared collections.

Additional prior warning: MauiBot requested asymmetric live/dead sibling coverage for shared stops. Current GradientBrushMemoryTests.AliveBrushStillInvalidatesAfterSiblingBrushIsCollected covers this.

Blast Radius Assessment

  • Runs for all instances: Yes. BindableObject, Element, BindingBase, MultiBinding, dispatcher lookup, and GradientBrush shared Controls code are affected for all platforms.
  • Startup impact: Low direct startup impact, but BindableObject.Dispatcher, Element.Parent, and BindingContext access are common framework paths.
  • Static/shared state: No new global static state; new state is per-object/per-subscription helper.

CI Status

  • Required-check result: undetermined
  • Classification: undetermined
  • Action taken: gh was unavailable due missing authentication; confidence capped. User-provided gate result was already inconclusive and not rerun.

Findings

No high-confidence ❌ Error, ⚠️ Warning, or 💡 Suggestion findings were identified in the current local diff by the expert reviewer.

Failure-Mode Probing

  • Shared non-empty collection with transient brush: weak collection and stop proxies should avoid collection/stop event handlers rooting the brush.
  • Dead brush with surviving stop and inherited binding context: PR marks stale inherited context for cleanup and dispatches or drains cleanup safely before exposing normal BindingContext values.
  • Dead sibling brush with live sibling sharing same stop: owner/occurrence-aware cleanup avoids clearing the live brush parent and weak proxies preserve live invalidation.
  • Dispatcher unavailable or disposed during finalizer cleanup: cleanup remains pending instead of resolving services from finalizer or surfacing ObjectDisposedException.
  • Reparent/rebinding while cleanup is pending: unique pending cleanup tokens prevent stale dispatcher callbacks from clearing newer cleanup generations.

Verdict: NEEDS_DISCUSSION

Confidence: medium for code correctness, low for merge readiness because CI/gate status is undetermined in this environment.
Summary: The current PR fix is technically coherent and well-covered, but it is broad. Alternative candidates should explore whether a narrower GradientBrush-only fix can satisfy the original leak without touching global BindableObject / Element binding-context and dispatcher semantics.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix GradientBrush-only weak subscriptions and no GradientStop.Parent assignment ✅ PASS focused leak/invalidation tests 2 files Smaller than PR, but behavior-breaking: drops parent/resource/dispatcher semantics and does not cover binding-source cleanup.
2 try-fix GradientBrush weak subscriptions while preserving GradientStop.Parent; rely on existing weak RealParent ❌ FAIL stronger inherited-context regression 2 files Fixes original brush leak, but stale inherited binding context/value remains after owner brush collection.
3 try-fix Weak subscriptions plus lazy Element cleanup when weak parent is observed collected ❌ FAIL no-dispatcher/no-access binding-source regression 4 files Passes original leak, lazy inherited cleanup, and adjacent BindableObjectUnitTests, but cannot release binding-held source without later access.
PR PR #36521 Weak subscriptions plus weak inherited binding-context storage, pending cleanup tokens, dispatcher-aware cleanup, and MultiBinding proxy handling ⚠️ INCONCLUSIVE (Gate) 10 files Original PR; broader blast radius, but alternatives converge on its required ingredients.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Proposed lazy collected-parent cleanup after Candidate 2 failed inherited-context cleanup.
maui-expert-reviewer 2 No NO NEW IDEAS: passing no-access source release requires weak inherited binding context or active dispatcher-aware cleanup, converging on PR #36521.

Exhausted: Yes
Selected Fix: PR #36521 — The narrower candidates either break existing GradientStop.Parent semantics or fail inherited BindingContext/source-release regressions. Candidate 3 is the closest smaller alternative but still fails a memory-release scenario that the PR explicitly covers.


🏁 Report — Final Recommendation

Comparative Candidate Report — PR #36521

Candidate ranking

Rank Candidate Regression result Assessment
1 pr ⚠️ Gate inconclusive; focused with-fix logs show PASS Best available fix. It fixes weak collection/per-stop event retention while preserving GradientStop.Parent, inherited binding context, dispatcher, binding replacement/removal, and MultiBinding semantics covered by the added regressions.
1 pr-plus-reviewer Same as pr Equivalent to pr; no actionable expert-review feedback was available to apply.
3 try-fix-1 ✅ PASS focused leak/invalidation tests Smaller, but behavior-breaking because it stops assigning GradientStop.Parent; this risks resource lookup, dispatcher discovery, and inherited-context behavior and does not cover binding-source cleanup.
4 try-fix-3 ❌ FAIL CollectedBrushWithoutDispatcherReleasesBindingContextPropertyBindingSource Closest narrower alternative, but lazy cleanup cannot release a binding-held inherited source without later access or dispatcher availability. Must rank below passing/inconclusive non-failing candidates.
5 try-fix-2 ❌ FAIL CollectedBrushClearsGradientStopInheritedBindingContext Weak subscriptions while preserving parent are insufficient because stale inherited context/binding values remain after the owning brush is collected. Must rank below candidates that did not fail regression tests.

Comparative analysis

try-fix-1 demonstrates that the original event-subscription leak can be fixed with a much smaller GradientBrush-only diff, but it does so by removing the existing parent relationship. That trades a memory leak for a semantic change in GradientStop ownership and leaves the broader inherited-binding-source leak unaddressed.

try-fix-2 preserves the parent relationship and uses weak event proxies, but the stronger inherited-context regression fails. This shows the original PR is not merely over-engineering around event handlers: preserving existing parent semantics creates stale inherited binding context that must be cleaned up.

try-fix-3 adds lazy collected-parent cleanup and passes the original leak plus adjacent BindableObject tests, but it still fails the no-dispatcher/no-access binding-source regression. That failure is decisive because it is exactly the scenario where a dead owner must stop retaining an inherited source without relying on later app access.

The PR fix contains the ingredients the alternatives converged toward: weak event proxies, owner-aware parent cleanup, weak inherited binding-context storage for binding sources, dispatcher-aware pending cleanup, generation tokens, and MultiBinding proxy handling. Its blast radius is broader, but the narrower candidates either break existing behavior or fail strengthened regressions.

Winning candidate

Winner: pr

The raw PR fix wins because no reviewer patch was needed and every narrower try-fix candidate has either a known semantic regression or a failed strengthened regression. The gate remains inconclusive due the without-fix build issue, so this is a code-merit recommendation rather than a claim that CI is fully verified.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@kubaflo

kubaflo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo): The current-head MauiBot review 4708635685 reports 0 errors, 0 warnings, and 0 suggestions, exhausts the alternative approaches, and selects the submitted PR as the winner. Its gate is inconclusive only because the without-fix checkout cannot compile a regression test that references the fix-only ClearRealParentAndInheritedContextIfCollected API; all three corresponding with-fix suites pass. There is no actionable review change to apply, so the active merge-CI batch remains the next source of evidence.

@kubaflo

kubaflo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated merge-readiness analysis (GitHub Copilot CLI, on behalf of @kubaflo)

Good for merge from the automated review/CI perspective.

  • maui-pr 1511365 — succeeded.
  • device 1511380 — succeeded; published Android/iOS/MacCatalyst/Windows results contain 0 failed tests.
  • UI 1511373 hit pipeline-wide Shell/Layout 180-minute timeouts. The only discrete assertion was an unrelated Appium socket hang up in AbsoluteLayoutFeatureTests; the Brush-specific jobs had no failed tests.

The current-head reviewer already reported gate passed and zero findings. No GradientBrush/BindingContext regression is present. This PR is still a draft, so it must be marked ready for review before GitHub can merge it.

@kubaflo

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor Author

Tests Failure Analysis

@kubaflo — test-failure review results are available based on commit 049ec12.
To request a fresh review after new comments, commits, or CI runs, comment /review tests.

Overall Not ready Failures 14 Regressed vs base 6 Baseline 0 on base

Test Failure Review: Not ready - click to expand

Overall verdict: Not ready. Six UITest failures are deterministic regressions vs base — red on this PR but green across all 5 recent main builds sampled for maui-pr-uitests and red on none — so they are treated as likely PR-caused. None of the 14 distinct failures also fail on the base branch (0 pre-existing/known dismissable), and a further 8 could not be attributed (build-error publish steps, aborted macOS legs, and flaky-on-base crashes), so a human must classify them.

  • ✗ PR-related — regressed-vs-base UITest failures (~6 tests): red on the PR yet green on all 5 sampled main builds and red on none; spans drag-and-drop, layout and indicator tests including FlexLayout_BasisFixed_DirectionRowReverse (app expected running still — possible crash).
  • i Uncertain — unexplained/aborted build legs + unattributed publish errors (~15 legs, 2 aborted checks, ~8 unattributed): 15 failed legs produced no extractable failure, 2 macOS Controls checks were CANCELLED, and Publish the ... test results - build error steps plus flaky-on-base crashes (PickerDialogDoesNotCrashWhenPagePoppedWhileDialogOpen, ref #36536) are neither provably PR-caused nor dismissible.

Coverage: 162 checks · 153 passing · 9 failing · 0 pending · 0 inaccessible · 1 unmapped · 15 unexplained build legs · 0 unaccounted failing checks · 2 aborted failing checks · 0 canceled-build checks · 0 device-test unverified · 8 unattributed · 6 regressed-vs-base. Deterministic ceiling: Not ready — 6 legs regressed vs base plus unexplained/aborted legs and unattributed failures.

Builds (this PR): maui-pr-uitests 1511373, maui-pr-devicetests 1511380 (device tests confirmed clean). Base sampling (main, 5 recent builds per definition): 1503618, 1503332, 1503036, 1499834, 1499259.

Recommended action

Do not merge yet. A human should investigate the 6 regressed-vs-base UITest failures (drag-and-drop, layout, indicator, FlexLayout crash) against this PR's GradientBrush change, and read the 15 unexplained legs and 2 cancelled macOS Controls checks before trusting a green result.

@kubaflo

kubaflo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated follow-up analysis (GitHub Copilot CLI, on behalf of @kubaflo)

The /review tests claim of six deterministic regressions is a per-leg classification error; all six tests passed on one attempt and failed on the other within UI build 1511373.

Main build 1511365 and device build 1511380 passed on the same merge, with all seven Helix jobs clean. No failure stack enters the changed GradientBrush/BindingContext cleanup. No PR code change is required; the draft remains good for merge from the automated code/CI perspective.

@kubaflo

kubaflo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🔍 AI-generated (GitHub Copilot CLI, on behalf of @kubaflo)

Closing as over-scoped / too risky to merge as-is. The core GradientBrush.cs change is a sound weak-proxy fix for #36363 (base still uses strong CollectionChanged/PropertyChanged subscriptions at GradientBrush.cs:64/71/98, so the leak is real). However, this PR couples it to a framework-wide refactor that is out of scope for a GradientStops leak:

  • BindableObject.cs — ~364 changed lines introducing a new dispatcher-based inherited-BindingContext cleanup subsystem (Dispatcher property, InheritedBindingContextReference, PendingInheritedBindingContextCleanup, TryScheduleDispatch, MarkInheritedBindingContextForCleanup, changed TryGetRealParent).
  • Element.cs (~104 lines), DispatcherExtensions.cs (FindDispatcherTryFindDispatcher(includeParents)), BindingBase.cs, MultiBinding.cs.

Changing the core binding/dispatcher infrastructure to support finalizer-time context clearing risks every BindableObject in the framework and is not what #36363 asks for.

Recommendation: re-do as a focused, self-contained fix in GradientBrush.cs only — convert the strong GradientStopCollection subscriptions to WeakNotifyCollectionChangedProxy/WeakNotifyPropertyChangedProxy and clear GradientStop.Parent on detach, exactly like the merged Shapes fixes (#36526 GeometryGroup, #36531 TransformGroup, #36535 PathGeometry, #36547 PathFigure) — with no changes to BindableObject/Element/dispatcher/binding infrastructure. #36363 stays open for that focused PR.

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

Labels

agentic-workflows perf/memory-leak 💦 Memory usage grows / objects live forever (sub: perf) s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

3 participants