fix: regression with StepMutator running multiple times in some cases#3259
Conversation
Greptile SummaryThis PR fixes a regression where a
Confidence Score: 5/5The change is safe to merge; the fix is minimal and well-tested across the two known execution orderings. The dual-ordering logic ( No files require special attention; the two duplicate test cases in Important Files Changed
Reviews (7): Last reviewed commit: "add more unit tests for coverage" | Re-trigger Greptile |
Canon PR Review — Engineering Standards ScorecardPR: fix: regression with StepMutator running multiple times in some cases Scores
Decision: APPROVE Violations FoundModule DesignCC-028 — Interface Comments (what + why) (Source: A Philosophy of Software Design)
MaintainabilityCC-093 — Fix Broken Windows (Source: The Pragmatic Programmer)
What's Working Well
To UnblockNothing is blocking merge. Two follow-ups worth tracking (not required for this PR):
Scored against: CC-003, CC-005, CC-006, CC-011, CC-021 (Code Quality) | CC-027, CC-015, CC-028, CC-017 (Module Design) | CC-014, CC-048, CC-056, CC-058, CC-060 (Reliability) | CC-019, CC-031, CC-093, CC-107 (Maintainability) |
|
Discussed async - details here. |
Follow-up: Updating decision to REQUEST_CHANGES<@talsperre> shared a detailed counter-review with 4 findings. After cross-checking against the head commit ( Finding 1 (late-before-init still duplicates) — Disagree on current codeThe gist says if deco._mutate_called or deco._late_mutate_called:
continueSo if the late pass ran first and set However, I'd recommend a targeted test to confirm — Finding 2 (name-based cleanup deletes wrong outputs for two instances of same class) — Confirmed
d.inserted_by[0] == mutator_name...uses Finding 3 (late-pass re-run leaves replacement decorator uninitialized) — Confirmed, more criticalScenario B (normal init ran first, then kubernetes late-attaches):
The replacement Finding 4 (test misses Findings 2 and 3) — ConfirmedThe existing test only asserts Updated decision: REQUEST_CHANGESFinding 3 is the blocking issue. The proposed fix in the gist (id-based tracking + explicit re-init of replacement outputs) addresses it correctly. At minimum, the late-pass re-run path needs to call Scored against: CC-014 (silent failure mode on uninitialized decorator — Reliability), CC-107 (regression test doesn't catch the fixed behavior — Maintainability) |
npow
left a comment
There was a problem hiding this comment.
All three findings addressed in the final commit:
Finding 3 (was blocking) — resolved. id-based tracking (_mutate_inserted) now captures exactly which decorator/wrapper objects each mutator run produces. _remove_step_mutator_outputs removes by object id (not by name), so two instances of the same mutator class no longer cross-delete. reinit_decorator_ids feeds back into the late-attach init loop so replacement decorators get external_init called — the missing initialization is now explicit.
Finding 2 (name-based cross-delete) — resolved. Replaced by id-based cleanup.
Finding 1 (late-before-init duplicate) — resolved. _init_step_decorators now skips a mutator that already set _mutate_called, preventing the double-run when conda/pypi calls _process_late_attached_decorator before _init_step_decorators.
Tests cover the late-before-init ordering, multiple distinct late attachments, and the replacement re-init path. All CI green. LGTM — good to merge.
PR Type
Summary
Issue
Fixes #3258
Reproduction
Runtime:
Commands to run:
# paste exact commandsWhere evidence shows up:
Before (error / log snippet)
After (evidence that fix works)
Root Cause
Why This Fix Is Correct
Failure Modes Considered
Tests
Non-Goals
AI Tool Usage