Skip to content

fix: recover from warm-state scope corruption (reboot + retry)#3

Merged
fdaviddpt merged 1 commit into
mainfrom
fix/recover-warm-scope-corruption
Jun 21, 2026
Merged

fix: recover from warm-state scope corruption (reboot + retry)#3
fdaviddpt merged 1 commit into
mainfrom
fix/recover-warm-scope-corruption

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Context

The warm Rector daemon intermittently surfaced a false rector.errorCall to a member function toMutatingScope() on null (PHPStanNodeScopeResolver:177) — after an edit, while a cold rector process on the same file passes. Root cause: stale PHPStan scope/reflection state across warm calls. resetReflectionState() (the #273 fix) only flushes ResettableInterface services (source locators); PHPStan's scope/reflection caches aren't resettable, so it can't clear this.

Change

  • RectorTool::process detects internal warm-corruption errors (toMutatingScope / must be resolved / System error), calls RectorRunner::reboot() (drop container) and retries once on a fresh container — cold-quality result instead of a false error to the caller.
  • Extract RunnerInterface + RectorTool::withRunner() test seam (keeps __construct's concrete RectorRunner type so MCP SDK autowiring is unaffected — widening it to the interface breaks autowiring with -32603).
  • Bump 0.3.0 → 0.4.0.

Tests

3 new unit tests (TDD red→green): recovers from corruption / does not retry unrelated errors / surfaces error if retry also fails. Unit 6/6, integration back to baseline (25 assertions, 1 skip).

Could not reproduce the original NPE in a fresh harness (56+ warm calls) — it's cross-session accumulation in the long-lived daemon. The fallback is the robust, version-independent safety net regardless of the exact trigger.

🤖 Generated by Max — a fresh container forgets all sins.

The warm Rector container can carry stale PHPStan scope/reflection state
across edits — a class whose shape changed on disk between calls yields a
null scope deep in PHPStanNodeScopeResolver ("Call to a member function
toMutatingScope() on null"). It is not a real finding: a cold run on the
same file passes. resetReflectionState() only flushes ResettableInterface
services (source locators); PHPStan's scope/reflection caches are not
resettable, so it cannot clear this.

RectorTool::process now detects these internal corruption errors
(toMutatingScope / "must be resolved" / "System error"), reboots the
container (RectorRunner::reboot()) and retries once — a fresh container is
the only guaranteed reset. Result is cold-quality instead of a false
rector.error surfacing to the caller.

Extract RunnerInterface so the recovery path is unit-testable with a double
that simulates the corruption; RectorTool::withRunner() keeps __construct's
concrete type for MCP SDK autowiring.

Bump 0.3.0 -> 0.4.0.

Co-Authored-By: Max <noreply>
@fdaviddpt fdaviddpt merged commit fb00629 into main Jun 21, 2026
3 checks passed
@fdaviddpt fdaviddpt deleted the fix/recover-warm-scope-corruption branch June 21, 2026 21:38
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