RecipeRun does not distinguish a fixpoint from an exhausted cycle budget

Open
#8,498 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
45/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
java
Domain
devtools

Research direction

Start with RecipeScheduler.runRecipeCycles and RecipeRun, then inspect how RecipeRun is constructed and consumed. Trace the three loop exit paths—fixpoint, panic, and exhausted cycle budget—and determine how the cycle count or termination reason should be exposed. Done means callers can distinguish these outcomes instead of seeing only the changeset and dataTableStore.

Written by the indexing model from the issue text.

Description

enhancement

Hi — an observation about RecipeScheduler.runRecipeCycles and RecipeRun, not a bug report. It comes from a methodological result on iterated structural repair I deposited recently, where this exact distinction turned out to be the difference between a measurement and an artifact.

What the code does.

runRecipeCycles leaves its loop by three different paths:

  1. fixpoint — i >= minCycles && (cycle.getMadeChangesInThisCycle().isEmpty() || !anyRecipeCausingAnotherCycle)
  2. panic — ctx.getMessage(PANIC) != null
  3. budget — the loop condition i <= maxCycles fails

All three reach the same return new RecipeRun(after.getChangeset(), ...), and RecipeRun has two fields: changeset and dataTableStore. Neither records the cycle reached nor which exit was taken.

So a run that reached a fixpoint and a run that was still making changes when the budget ran out are indistinguishable to the caller.

Why the distinction is load-bearing.

I built an engine that repairs a typed graph by iterated descent — each step strictly reduces an integer count of rule violations — and measured how often it fails to reach a clean state. Its halt states are: clean; stuck, meaning no available step reduces the count; and exhausted, meaning the step budget was reached.

The finding was that the third is not an outcome, it is an artifact control. Only measurements where exhaustion is zero say anything about the system. Where it is nonzero, "did not finish clean" is a statement about the budget, not about the input. In my sweep an apparent regularity in preliminary data dissolved into sampling noise once that control was enforced across every cell — without it I would have reported a false result.

The same asymmetry holds here, and it is not only a measurement concern. "The recipe made no further changes" is a property of the recipe and the codebase. "The recipe was cut off at cycle N" is a property of the configuration. Collapsing the two means a fully applied migration and a partially applied one report identically, and the difference surfaces only when someone runs the recipe again and gets a fresh diff.

  • This is visible in the project's own history. #2415 was closed with:

recipe execution is designed to continue processing until either no changes are detected, or the max number of cycles is exceeded

which is accurate, and states the two exits as if they were the same kind of ending.

Concrete question.

Would you consider adding to RecipeRun either:

(a) the cycle count reached; and/or

(b) the termination reason — fixpoint, panic, or cycle budget exhausted.

(b) is the one that carries the information. Both values exist inside runRecipeCycles at the point of return: the loop variable i, and which branch was taken.

It would let a build warn, or fail, on "changes were still being made when the budget ran out" — which today can only be discovered by running again and seeing a new diff.

Reference, if the argument is of interest: Two Regimes of Failure in Structural Repair, https://doi.org/10.5281/zenodo.21249907 — §4.2 defines the halt states and §4.6 is the case where the control caught a result that would otherwise have been reported.

Dominant language
Java
Stars
3.9k
Forks
599
Avg merge
9h 38m
Merged PRs (30d)
557

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openrewrite/rewrite

All issues in openrewrite/rewrite

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.