rectorphp/rector · error · ShouldNotHappenException

These rules from "$rectorConfig->skip()" do not exist - remo

Error message

These rules from "$rectorConfig->skip()" do not exist - remove them or fix their names:

What it means

Rector validates every entry passed to RectorConfig::skip() (src/Config/RectorConfig.php:126) and throws a ShouldNotHappenException listing all skip entries that are not existing classes. The skip list is matched against real Rector rules, paths and class names, so a typo'd or renamed rule name can never match anything and would silently disable the skip - Rector refuses to run instead of ignoring your intent. This check runs when the rector.php config is loaded, before analysis starts.

Source

Thrown at src/Validation/RectorConfigValidator.php:63

            if (!self::isRectorClassValue($value)) {
                continue;
            }
            if (class_exists($value)) {
                $skippedRectorRules[] = $value;
                continue;
            }
            $nonExistingRules[] = $value;
        }
        SimpleParameterProvider::addParameter(Option::SKIPPED_RECTOR_RULES, $skippedRectorRules);
        SimpleParameterProvider::addParameter(Option::SKIPPED_NON_RECTOR_CLASSES, $skippedNonRectorClasses);
        if ($nonExistingRules === []) {
            return;
        }
        $nonExistingRulesString = '';
        foreach ($nonExistingRules as $nonExistingRule) {
            $nonExistingRulesString .= ' * ' . $nonExistingRule . \PHP_EOL;
        }
        throw new ShouldNotHappenException('These rules from "$rectorConfig->skip()" do not exist - remove them or fix their names:' . \PHP_EOL . $nonExistingRulesString);
    }
    /**
     * Only Rector rules are matched against skipped classes, so any other class can never be skipped
     */
    private static function isNonRectorClass(string $key): bool
    {
        // interfaces are allowed, as they can mark a group of Rector rules
        if (!class_exists($key)) {
            return \false;
        }
        if (is_a($key, RectorInterface::class, \true)) {
            return \false;
        }
        return !is_a($key, PostRectorInterface::class, \true);
    }
    /**
     * @param mixed $value
     */

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Read the exception message: it lists every non-existing entry with ' * ' prefix; remove those entries from skip() or correct their spelling to the real FQCN.
  2. If the rule was renamed in a newer version, find the new class name (vendor/rector/rector-src, your IDE, or the vendor package changelog) and update the skip entry to the new class-string.
  3. Verify each remaining entry resolves: run a quick check like `php -r "var_dump(class_exists($argv[1], true) || interface_exists($argv[1], true));" SomeRectorClass` for each skip entry.
  4. Pin or align versions: if the skip entries come from a ruleset package, make sure your rector/rector version matches the one that package expects, so the skipped class names exist again.

Example fix

// before
$rectorConfig->skip([
    Rectify\TypeDeclaration\Rector\Property\TypedPropertyRector::class, // typo + wrong namespace
    Rector\CodeQuality\Rector\If_\CombinedIfRector::class,
]);

// after
$rectorConfig->skip([
    Rector\CodeQuality\Rector\If_\CombinedIfRector::class,
]);
Defensive patterns

Strategy: validation

Validate before calling

// before calling $rectorConfig->skip() in rector.php
$skip = [
    Rector\CodeQuality\Rector\If_\CombinedIfRector::class,
    'Rector\TypeDeclaration\Rector\Property\MaybeTypedRector', // typo, would throw
];
$nonExisting = array_filter($skip, function ($entry): bool {
    if (!is_string($entry)) {
        return false; // path or fn-style entries, not class names
    }
    return !class_exists($entry) && !interface_exists($entry);
});
if ($nonExisting !== []) {
    throw new InvalidArgumentException('Skip entries do not exist: ' . implode(', ', $nonExisting));
}
$rectorConfig->skip($skip);

Type guard

function skipEntriesExist(array $skip): bool
{
    foreach ($skip as $entry) {
        if (is_string($entry) && !class_exists($entry) && !interface_exists($entry)) {
            return false;
        }
    }
    return true;
}

Prevention

When it happens

Trigger: Calling $rectorConfig->skip([...]) with a misspelled class name (e.g. 'TypeDeclarationRector' instead of the real FQCN); skipping a rule class that was renamed or removed in a newer rector/rector or vendor package version; skipping by constant from an old SetList (e.g. SetList::DEAD_CODE string value) instead of a rule class name; entries left over after the rule they excluded was deleted from the codebase.

Common situations: Upgrading rector/rector (or a ruleset package) to a major version where rule classes were renamed, and old skip entries no longer resolve; copy-pasting skip lines from blog posts or another project's rector.php that reference rules your installed version does not contain; CI suddenly failing after composer update although nothing in the repo changed, because the vendor rule class disappeared; refactoring where the excluded rule was deleted and the skip entry was forgotten.

Related errors


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/1457aaffb06362a2. Report an issue: GitHub.