
When I started using Claude Code for TYPO3 upgrades, I did everything in one session. For a small site like my own hoja.in that works. For a large multi-site installation with several sites and many own extensions, it does not. The agent reads TypoScript of site A, then TCA of extension B, then the changelog, then the error log, and after a while the context is full of things which are no longer relevant. It starts to forget the plan, or worse, it remembers half of it.
The way out was not a bigger context. It was splitting the work, the same way a team would do it: people who look around, people who change code, and one person who tries to find what everybody else missed. In Claude Code these are subagents.
What a subagent is (and is not)
A subagent is a separate Claude instance with its own context window, its own system prompt and its own set of tools. The main session hands it a task, the subagent works on it, and only the final report comes back. All the files it read and all the dead ends it tried stay in its own context. That is the whole trick: the main session keeps the conclusions, not the noise.
You define them as Markdown files with YAML frontmatter. Project subagents live in .claude/agents/ and are shared with the team through git; personal ones live in ~/.claude/agents/. Only name and description are required. The body of the file becomes the subagent’s system prompt.
Step 1: read-only scouts
Before any code changes, I want an inventory of what the next hop breaks. Scouts do this per area: system configuration, site configuration, TypoScript and TSconfig, templates, forms, own extensions. They must not change anything, so they get no edit tools at all:
---
name: upgrade-scout
description: Read-only sweep of ONE area of a TYPO3 project for what a given upgrade hop breaks. Give it the area, the hop (e.g. 13 -> 14) and where the changelog is. Returns findings with file:line. Use several in parallel.
tools: Read, Grep, Glob
model: sonnet
---
You inspect one area of a TYPO3 project for a single upgrade hop.
Rules:
- You never edit files and never run commands that write anything.
- Check every suspicion against the core changelog in vendor/typo3/cms-core/Documentation/Changelog/<version>/.
Name the changelog file for each finding. No changelog entry, no finding.
- Report: file:line, what breaks, changelog reference, suggested fix in one sentence.
- If the area is clean, say so. Do not invent work.The tools field is an allowlist: the scout gets only Read, Grep and Glob. Then I ask the main session something like: “Send one upgrade-scout each for config/system, config/sites, the sitepackage TypoScript, the Fluid templates and the forms, in parallel.” They run at the same time, each with a fresh context, and I get five short reports instead of one giant session.
The rule “no changelog entry, no finding” is important. A language model knows a lot about TYPO3, but it also remembers APIs from v9 next to APIs from v14. Forcing it to cite the real changelog file from vendor/ removes most of the guessing.
Step 2: workers with disjoint file sets
From the scout reports the main session writes a plan with work packages. The key rule for workers: every package gets its own set of files, and no two packages touch the same file. Otherwise two workers edit the same TCA override in parallel and one silently overwrites the other. Finding such a lost edit later costs more time than the parallelism saves.
---
name: upgrade-worker
description: Applies the migration fixes of ONE work package from .project-migration/plan.md inside its assigned file set, and runs the given verification command before returning.
tools: Read, Edit, Write, Grep, Glob, Bash
---
You get: a list of files you own, the target TYPO3 version, the findings to fix,
and a verification command.
Rules:
- Edit only files on your list. If a fix needs another file, stop and report it.
- One finding, one small change. Do not refactor what is not broken.
- Run the verification command at the end and paste its real output.
- Never touch the database, composer.json or composer.lock.A worker for “own extension A, Classes and Configuration” and a worker for “sitepackage templates” can run in parallel. Composer changes, database work and upgrade wizards stay in the main session, one at a time, because they touch everything.
The tool list here is wider, so a guard is useful. For my own migrations I use a PreToolUse hook that blocks commands writing the database when there is no fresh dump in the backups folder. The subagent cannot “decide” to skip it, because hooks are enforced by the harness and not by the prompt.
Step 3: an adversarial reviewer at the end of every hop
This is the part I would never skip again. After the workers are done and the gate is green (composer validate, cache flush, no pending wizards, smoke test against the baseline), one more subagent reads the whole diff with a single job: find what everybody else missed.
---
name: upgrade-reviewer
description: Adversarial reviewer for one finished hop of a TYPO3 upgrade. Reads the hop's diff, the gate output, the ledger and the plan, and hunts for what the gate misses. Read-only. Use at the end of every hop.
tools: Read, Grep, Glob, Bash
---
Assume the hop is NOT done. Look for:
- loosened version constraints or ignored platform requirements
- wizards marked done without migrated data
- configuration still pointing to old paths
- features silently dropped (a plugin, a field, a redirect)
- a runbook that differs from what was actually run
Every finding: file:line, why it matters, severity (blocker / should fix / note).
You do not fix anything.It has Bash for git diff and git log, but its prompt says it fixes nothing, and it reports back to me, not to the workers. Is a prompt enough to stop it from editing? For read-only roles I prefer to leave Edit and Write out of the tool list, as here. Bash can still write files, though, so for the reviewer the prompt and the hook remain the guard. The scout has no Bash at all, and there it is really not a question of good behaviour.
On hoja.in, which is a tiny project, the reviewer still came back with real findings. Two examples from my ledger: the imported pages were owned by the _cli_ user instead of my admin account, and config/system/additional.php was missing from git because DDEV was still managing it. Both would have reached production. The tests were green in both cases, because no test looked for them.
What I learned
- Scouts are cheap and parallel. Give them narrow areas and read-only tools.
- Workers need disjoint file sets. Shared files go to the main session.
- Database, Composer and wizards stay serial and in the main session, behind a hook.
- The reviewer’s prompt starts from “assume it is not done”. A friendly reviewer is useless.
- Subagents return reports, not truth. Tests, the smoke comparison and my own review decide what ships.
One more honest point: subagents are not free. Each one starts fresh and must read its files again, so for a small upgrade a single session is faster. I start splitting when one session cannot hold the plan and the findings at the same time.
Feedbacks are the champion’s breakfast. If you have a different split that works for you, drop a comment!