Chat on WhatsApp
Back to /claude
Claude code cluster

Claude Code Anti-Patterns to Avoid in Magento 2 Projects

AI-paired Magento work fails in twelve recognisable ways. ObjectManager in production, vendor edits, preferences over plugins, no eval loop. This page names each one, explains why it bites, and shows the cleanest fix.

What to avoid

10 anti-patterns Claude (and you) keep falling into

These are the bad shortcuts I catch in code review every week. Add them to your CLAUDE.md so the model stops shipping them.

Pastes as a markdown bullet list — drop it straight into your project CLAUDE.md.

Tip: paste the 10 rules at the top of your CLAUDE.md under a "## Magento anti-patterns — never do these" heading. Claude reads CLAUDE.md before every turn.

More in the cluster

Keep going

FAQ

Common questions

Why is ObjectManager::getInstance in production code such a big deal?

It bypasses the entire DI container, which means: (1) plugins on the resolved class never fire — including security plugins from third-party modules; (2) the class can't be mocked for unit tests, so coverage drops; (3) preferences in etc/di.xml are ignored, so a customer who overrode the class gets the default instead. Adobe's EQP checklist auto-fails the PR. The only legal locations are Setup/Patch/ (no DI yet at install time), Test/Unit/ (mocking framework needs it), and Block/ if you literally have no choice (you do have a choice — use a ViewModel). Replace every call with constructor injection.

Claude suggested editing vendor/magento — why is that a hard no?

Three reasons. (1) Composer wipes vendor/ on every composer install — your edit vanishes silently in CI. (2) Adobe's security patches replace the file on the next composer update, taking your fix with it but also reintroducing whatever bug you fixed. (3) Marketplace tech-review hard-fails any module that ships diffs against vendor. The right pattern: copy the file's logic into a plugin (around if you need to short-circuit, after for mutation), or a preference if you really must replace. Add a CI guard: git diff --name-only origin/main HEAD | grep '^vendor/' && exit 1.

What's wrong with using preferences for everything?

Preferences don't compose. Two modules that both preference the same class can't coexist — whichever loads later wins, and the loser's logic vanishes silently. In a Magento store with 30+ third-party modules, this is roulette. Plugins compose: ten modules can around-plug the same method and they all run, ordered by sortOrder. The fix: 90% of the time you want a plugin, not a preference. Reserve preferences for: replacing a non-public method, overriding a final class, or changing constructor signature. And even then, prefer a virtualType + plugin first.

Claude wrote 200 lines of code on the first shot — should I trust it?

No, and the warning sign is exactly that — large, clean-looking output with no error feedback. Without an eval loop (php -l, phpcs, phpstan, MFTF) running automatically, the model is operating blind. Common silent failures in Magento: (a) typed against an interface that doesn't exist in your version (e.g. used Magento\\Customer\\Api\\Data\\AddressInterface when the calling code wanted QuoteAddressInterface); (b) wired plugin in etc/di.xml instead of etc/frontend/di.xml; (c) used GraphQL @resolver syntax that compiled, but the schema cache still serves the old AST. Always run setup:upgrade and reload the affected page.

My Claude Code session keeps making the same mistake — how do I fix it?

This is context poisoning: a wrong assumption ended up in the conversation history early and now every reply reinforces it. Three fixes, in order: (1) /clear and start fresh with the correct constraint in your first message; (2) move the constraint into CLAUDE.md so it survives /clear; (3) if it's a class-of-mistake (e.g. "keeps using ObjectManager"), add a PostToolUse hook that greps for the pattern and feeds the failure back. Don't try to argue the model out of the mistake in-thread — you'll just spend tokens. Reset, anchor harder, and the next session lands clean.