Why WordPress is the hardest platform to hand an agent
Claude Code is very good at working inside a repository. WordPress is not, primarily, a repository. It is a PHP application whose behaviour is decided by three things at once: the files on disk, the rows in a database, and whichever of the forty plugins you installed happens to hook the filter you are looking at. Two of those three are invisible to a tool that reads files.
That is the whole difficulty, and everything below is a consequence of it. On a static site or an Astro project, reading the code tells you what the site does. On WordPress, reading the code tells you what could happen, and the database tells you what actually does.
The practical effect is that the reconnaissance step matters more here than on any other platform, and that a confident-sounding change is more likely to be wrong. An agent asked to "make the header sticky" will find a header template, edit it, and report success. Whether that template is the one your site renders depends on the template hierarchy, on whether a page builder is overriding it, on whether the active theme is a child theme, and on whether some plugin filtered the output on the way out.
None of that makes WordPress a bad target. It makes it a platform where the operational layer — a project context file that states the real facts, and checks capable of failing — earns its keep faster than anywhere else.
Reconnaissance, before you change anything
The first session on a WordPress project should change nothing. Its job is to establish which of the three sources of truth is in charge of what.
The tool for this is WP-CLI, because it can see the database. Give Claude Code permission to run read-only WP-CLI commands and ask for a report, not a fix:
Analyse this WordPress installation. Do not modify anything, and do not run any command that writes. Report, with the command output as evidence for each: 1. WordPress version, PHP version, and whether multisite is enabled. 2. The active theme, whether it is a child theme, and its parent. 3. Whether the active theme is a block theme or a classic theme. 4. Every active plugin, with version, and flag any that are known page builders, caching layers, or SEO plugins. 5. Which plugins register custom post types or taxonomies, and their names. 6. Whether a persistent object cache is configured. 7. Where uploads live and whether they are offloaded to external storage. 8. Constants defined in wp-config.php, with values redacted. Then state, in one paragraph, which parts of this site's output are controlled by theme files and which by database content or plugin filters. Where you cannot tell, say so rather than guessing.
Useful commands to allow, all read-only:
wp core version --extra
wp theme list --status=active --fields=name,version,parent
wp plugin list --status=active --fields=name,version,update
wp post-type list --fields=name,label,public
wp option get template
wp option get stylesheet
wp cli info
Two answers change everything that follows. Is it a block theme or a classic theme? A block theme keeps templates as HTML files in templates/ and its styling in theme.json; a classic theme uses PHP templates and functions.php. Advice for one is often actively wrong for the other. And is a page builder installed? If Elementor, Divi, Beaver Builder or similar is active, a large amount of page output lives in post meta rows, not in any file, and editing templates will appear to do nothing.
A local environment worth breaking
Do not let an agent work against production. This is not a WordPress-specific rule but the consequences are worse here, because a bad migration or a plugin activation can take a site down in a way a file revert will not fix.
Any of the common local stacks will do — wp-env, LocalWP, DDEV, Lando, or a plain Docker Compose file. What matters is not which one, but that three things are true:
| Requirement | Why |
|---|---|
| The database can be reset in one command | So breaking it deliberately, to test a check, is cheap |
| It runs the same PHP major version as production | PHP 8 deprecations that only appear on the server are a bad way to find out |
| Debug output is on and logged to a file |
WP_DEBUG with WP_DEBUG_LOG and WP_DEBUG_DISPLAY off, so the agent can read errors without them reaching a page |
wp-env is the lowest-friction option for theme and plugin work because it is a single JSON file the repository can carry, which means the environment is part of the project rather than part of your laptop:
{
"core": "WordPress/WordPress#master",
"phpVersion": "8.3",
"plugins": [ "." ],
"config": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": false,
"SCRIPT_DEBUG": true
}
}
Tell Claude Code the reset command explicitly, in CLAUDE.md. An agent that knows it can restore the database in five seconds will test destructive things properly instead of reasoning about them.
The architecture Claude has to be told about
Four pieces of WordPress architecture cause most of the confusion, and none of them is discoverable by reading a single file.
The template hierarchy
WordPress picks a template by walking a defined list from most specific to least. For a single post in the guides category it will look for single-post.php, then single.php, then singular.php, then index.php, taking the first that exists. An agent editing index.php to change a post page is editing a file that is never reached.
State the resolved template in the prompt rather than asking the model to infer it. WP-CLI cannot tell you directly, but the free wp template-hierarchy-style plugins can, and in a pinch a one-line template_include filter will print it.
Hooks
Actions and filters are the extension mechanism, and they are also the reason "just edit the template" fails. If a plugin filters the_content, your template change is applied and then modified. When output is wrong and the template looks right, the answer is almost always a hook.
The excerpt on category archives is being truncated to 20 words and I cannot find where. Do not change anything yet. Find every callback attached to `excerpt_length` and `get_the_excerpt` across the active theme and all active plugins. For each, give me the file, the line, the priority, and the plugin or theme it belongs to. Then tell me which one wins at render time and why.
The loop
Almost every template runs a query and iterates it. Changing what appears on a page is more often a query change (pre_get_posts) than a markup change, and the two have very different blast radii: a badly scoped pre_get_posts filter affects the admin, feeds, and every other query on the site.
Where "content" lives
Post content is a row. Widgets are options. Menus are terms. Block theme template edits made in the site editor are stored as wp_template posts that override the file on disk. That last one catches people constantly: you edit templates/single.html, deploy it, and nothing changes, because someone opened the site editor once and the database copy now wins.
A CLAUDE.md for WordPress
This is where the reconnaissance turns into something the agent reads on every session. Keep it short — around 150 lines is the working limit before instructions start competing with each other for attention. Everything below is specific enough to change behaviour; nothing in it is generic advice.
# CLAUDE.md
## Project
Client marketing site on WordPress 6.x, classic theme with a child theme.
Traffic is mostly organic search. Downtime is worse than a delayed release.
## Environment
- Local: `npm run env:start` (wp-env), PHP 8.3, matches production.
- Reset the database: `npm run env:reset`. This is cheap. Use it.
- Never run commands against production. There is no production access
configured here and there must not be.
## Theme
- Active theme: `acme-child`, parent `acme`. NEVER edit the parent —
it is updated from the vendor and edits are lost.
- Classic theme: PHP templates, `functions.php`. Not a block theme.
- Template resolution matters. Before editing a template, confirm which
one WordPress actually loads for that route and say which it is.
## Plugins
- Yoast SEO owns titles, meta descriptions and the sitemap. Do not write
competing meta tags in the theme.
- WP Rocket is active. Any front-end change must be verified with cache
cleared, or the check is meaningless.
- Advanced Custom Fields defines the field groups in
`acme-child/acf-json/`. Field changes go in that directory as JSON so
they are version controlled, never through the admin only.
## Standards
- WordPress Coding Standards. `composer lint` must pass.
- Escape on output, always: `esc_html`, `esc_attr`, `esc_url`, `wp_kses_post`.
- Sanitise on input. Verify a nonce and a capability on every write.
- Text domain is `acme`. All user-facing strings translated.
- Enqueue assets with `wp_enqueue_script` / `wp_enqueue_style` and a
version string. Never hard-code a `