Skip to content

Fix ScanningRecipe accumulator loss in preconditioned DeclarativeRecipes across RecipeRunCycles#6913

Merged
jkschneider merged 2 commits intoopenrewrite:mainfrom
pstreef:fix/bellwether-accumulator-loss
Mar 10, 2026
Merged

Fix ScanningRecipe accumulator loss in preconditioned DeclarativeRecipes across RecipeRunCycles#6913
jkschneider merged 2 commits intoopenrewrite:mainfrom
pstreef:fix/bellwether-accumulator-loss

Conversation

@pstreef
Copy link
Copy Markdown
Contributor

@pstreef pstreef commented Mar 10, 2026

Problem

When a DeclarativeRecipe has preconditions (e.g. Singleton), its getRecipeList() wraps sub-recipes as BellwetherDecoratedScanningRecipe. These wrappers are new ScanningRecipe instances created on every call, each with a new UUID-based accumulator key.

RecipeRunCycle creates a RecipeStack which caches getRecipeList() results in an IdentityHashMap — so within a single cycle, the wrappers are stable. But when a new RecipeRunCycle is created (as happens when the Moderne platform yields on large repositories), the fresh RecipeStack calls getRecipeList() again, producing new wrappers with different UUIDs.

The accumulator data stored on the rootCursor under the old UUID becomes unreachable. The new wrapper creates a fresh empty accumulator, and all scan data collected in previous batches is lost. The edit phase then finds nothing to change.

The wrapping cascades through the entire recipe tree — BellwetherDecoratedRecipe.getRecipeList() and BellwetherDecoratedScanningRecipe.getRecipeList() both call decorateWithPreconditionBellwether() on their delegate's children. So any ScanningRecipe at any depth under a preconditioned parent is affected.

Solution

Override getAccumulator() in BellwetherDecoratedScanningRecipe to delegate to the wrapped recipe's accumulator. This uses the delegate's stable UUID (assigned once at construction) instead of the wrapper's ephemeral one, so accumulators survive across RecipeRunCycle boundaries regardless of how many times the wrapper is recreated.

BellwetherDecoratedScanningRecipe gets a new UUID-based accumulator key
each time it is instantiated. Since DeclarativeRecipe.getRecipeList()
creates new wrapper instances on every call, a fresh RecipeStack (which
triggers new getRecipeList() calls) causes accumulators stored under old
keys to become unreachable.

Override getAccumulator() to delegate to the wrapped recipe's stable UUID.
@github-project-automation github-project-automation Bot moved this from In Progress to Ready to Review in OpenRewrite Mar 10, 2026
@jkschneider jkschneider merged commit 6b7837d into openrewrite:main Mar 10, 2026
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants