Claude Code bug hunts: subagent or session?
Parallel Claude Code subagents hunted bugs in two of my games while their test suites were green. What they found, the regressions their own fixes caused, and what verification threw out.
Contents
ASMR Logo Craft Block had 8,005 passing tests across 40 files and a clean lint run when I typed "hunt bugs in this game" into Claude Code. What came back was a list of 21 confirmed bugs, every one of them in behaviour the suite never exercised. One turned a player's 30 levels and 5,000 coins into level 1 and 125 coins, permanently.
I ran three rounds of this on that game and three on Picture Word Connect. This post is the method, what it found, what its own fixes broke, and what got thrown out on the way. It also answers the question I had before starting, because both answers to it ended up running at the same time: do you split this kind of work across subagents inside one session, or across separate sessions running side by side?
The rules each agent got
The session split the game into four areas — game rules, state and runtime, the board view, and the screens, shop and ads — and gave each to a subagent. All four ran in parallel under the same rules:
- Read code and run tests. Change nothing in the repo.
- Report a bug only with a repro test that fails. Anything traced through the code but not run gets marked unproven.
- Repro files go in the session's scratchpad, outside the repo.
- The main session re-runs every repro itself before anything reaches me.
What the agents ruled out turned out as useful as what they found. The rules agent dealt 90,000 levels and 16,000 daily puzzles through the solver and every one was winnable. That took the level generator out of the search.
What round one found
A selection, worst first:
- The cloud save is read once, at launch. If the silent sign-in fails then and the player signs in later (opening the leaderboard does it), the next save writes this phone's fresh progress over the cloud copy. The shared cloud-save package wrote whenever someone was signed in, whether or not it had ever read that player's save.
- A purchase was acknowledged to the store even when saving the coins failed. The coins lived in memory until the next restart, then were gone.
- Refunding Remove Ads never brought the ads back, because the save merge keeps the flag if either copy has it.
- A second queued "purchase arrived" popup had no React
key, so the component was reused. It could not be closed, and its invisible overlay blocked the whole screen until restart. - Android back closed the "Out of moves" popup, which is meant to be impossible to close, and left a dead board.
- About 1 in 200 tutorial deals started with a row already solved.
- A date moving backwards, which flying west across midnight will do, paid the daily and login rewards again.
Between the hunt and the fixes I landed 13 commits of refactoring that moved much of this code into shared packages. The same four agents re-checked their repros against the new code. None of the 21 had been fixed, seven had moved into packages along with the code, and the refactor had added a 22nd: a first win's coins were now paid after the progress write instead of before it, so an app killed in between saved the level as won and never paid its 25 coins.
Every fix has to fail first
The rule for fixes matched the rule for bugs. Each one got a regression test, built from the agent's repro, and the test had to fail on the old code. Checked the dull way:
# the fix and its new test are both in the working tree
git stash push -q -- src/tutorial.js # old code back, new test kept
npx vitest run src/rules.test.js -t "already solved" # must fail
git stash pop -q
npx vitest run src/rules.test.js -t "already solved" # must pass
All 22 fixes got one and the suite went from 8,005 tests to 8,047. It is the habit from breaking a check to watch it fail, applied to every fix rather than every check.
Then the device runs. All 21 iOS scenarios passed on an iPhone simulator. On Android, three scenarios failed on one emulator and passed on another. The first emulator's Play account owns Remove Ads, so the game correctly showed no banner and no app-open ad for the test to find.
The bugs the fixes caused
Every round after the first began with one agent whose whole job was the previous round's fixes. It found something each time.
Round two. After round one I had added a cloud sync whenever the app comes back to the foreground, to stop two phones spending the same coins. Leaving and returning while the splash was still up ran that sync before the game had loaded its saved data. It merged the cloud copy into empty stores and wrote the result over the phone's save. The repro kept 3 levels and 200 coins where 12 levels and 1,000 coins were stored. The fix ignores returns until the game's first sync.
Round three. In round two I keyed the coin ledger by the platform's install id, so a backup
restored onto a second phone would not share a ledger entry with the first. On Android that id
is ANDROID_ID, and it survives an uninstall and "Clear data". A reinstalled app started its
entry at zero under the key the cloud already held with higher counts, the merge kept the
larger count per key, and everything earned before the first sync vanished. The old random id
passed the same repro, which made a good control. The fix is a random id written to Android's
no-backup folder. On the emulator it stayed the same across a relaunch and changed after a
reinstall and after Clear data.
Picture Word Connect. Its round-two regression agent found four defects in my round-one fixes. One mattered: if storage failed while startup settled a "board closed on" marker that round one had added, the game would not open.
Across the three ASMR Logo Craft Block rounds that is 66 bugs fixed (22, then 19, then 25), and two of them came from my own earlier fixes.
What verification threw out
Re-running every repro was meant to catch bugs that were not bugs. It caught several, and a few faults in the checking itself.
testMode: truein the production bundle came up in two rounds of Picture Word Connect, once as high severity. It was deliberate. The production-readiness agent eventually read the commit that set it, which said the test ads were waiting on an outside approval. Code does not tell you that you are waiting on an email.- "Finishing the tutorial deletes the daily in progress" was by design. The tutorial counts as that day's daily.
- "The shop says Added! when the coins were not credited" did not hold up. The coins were credited, and the failed save was retried.
- Two agents disagreed about why two level tests failed. One blamed half-rebuilt level files,
the other a stale installed package. Both were right, and neither was a bug in shipped code:
it was my unfinished board work. The hint crash with reduced motion on,
ReferenceError: Cannot access 'flight' before initialization, was real and also only in that uncommitted work, not in the released 1.0.2. - One correct finding was dropped. Android 16 ignores the portrait lock on screens 600dp and wider unless the manifest declares the app a game. True, and I want the game to rotate on tablets.
- The verification needed verifying. After a fix, one repro still failed exactly as it had on
the old code. It was importing a stale
node_modulesfolder left in the scratchpad by earlier work, and bare imports resolved there first. Another repro would have passed for the wrong reason after its fix, because the popup it waited for now opened only after the first sync, and its setup never synced. - Device results under load meant nothing. In round three the machine's load average reached 398 with swap full, and iOS scenarios timed out closing ads. With the load back down to about 27, the same scenarios passed.
Agreement was the cheap signal. In round three, three agents found the same daily bug on their own: a daily left as its moves ran out reopened at zero moves, with no popup and every swap refused. That one did not need weighing.
Claude Code: subagent-driven vs parallel session
Both shapes ran in these sessions, and not because I planned it. The hunts ran as subagents of one session. Meanwhile other Claude Code sessions worked in the same repos on unrelated features: a skin restyle, and a move to Google's Next-Gen ads SDK.
Subagents report to one place. The coordinating session merged six reports into one ranked list, noticed when two agents found the same bug, and re-ran every repro. Across sessions, the only messenger would have been me.
Their permission prompts come to you, and declining one stops that agent. In round two each agent had its own git worktree, and writing a repro test inside it raised a prompt. I declined one, that agent stopped, and the other three stopped at the same wall. Round three hit it again, and the fix was to put the repro files in the session's scratchpad, outside any repo, where round one had kept them. In Picture Word Connect one agent started a level check that takes over an hour and could not stop it, because a permission rule blocked the kill.
They also live and die with the session. When mine ended mid-round, all four agents stopped. Worktrees belonging to agents that had changed nothing were cleaned up automatically, and in round three that included the worktrees the repros imported from, so the coordinator built its own worktree to verify in.
Parallel sessions share everything below the conversation: the branch, the stash, the
installed packages, the emulators and the machine's memory. Halfway through round two's fixes,
the fix worktree's node_modules vanished and its branch carried a commit I had not made:
another session's Next-Gen ads migration, committed there. The fix for "an interstitial can
open over a rewarded video" then had to go into the new ads package too. Round three ended
with a rebase over another session's restyle, three conflicting files, and the device runs
again on the combined code. Several pushes carried other sessions' commits, so before each
one the session listed them and checked that the package versions they needed were published. In
Picture Word Connect, restarting an emulator broke another session's device run, and
something I never identified kept staging my changes.
git stash belongs to the repository, not the session, so the fail-first check above got
stricter: a named stash, and a look at the stash list before popping anything.
My split after six rounds: a bug hunt goes to parallel subagents, read-only, with one session verifying everything they report. Separate features go to separate sessions, each in its own worktree, committing explicit paths. What I would not repeat is device verification while two other sessions are booting emulators on the same machine.
What I would keep
The suite was green and 66 bugs sat under it, many in timing, lifecycle and two-device paths that unit tests do not walk unless someone writes the walk. My build already reviews the mechanical mistakes; this was for the ones it cannot see.
Three rules made it work. No bug without a failing repro. No fix without a test that fails on the old code. And the first agent of every round audits the previous round's fixes, because two of the worst bugs in ASMR Logo Craft Block came from my own fixes.