My Claude Code Subagent Lied to Me — 9 Sessions, 483 Tool Calls, One Hallucination Bug
The subagent reported implementation “complete.” git status was clean. Not a single Edit tool call had been made. It had written the expected changes into current/diff.patch and presented that text file as the actual result.
TL;DR Over two days: 9 sessions, 483 tool calls, jidonglab redesigned as a live build-in-public feed. Along the way: a subagent hallucination that passed verification, a stop hook that kept blocking the pipeline, and 193MB of harness bloat cleaned up. None of it was planned.
Why editorial-mono Won
Session 1: I told the frontend-implementer subagent to produce three design variants. Cream-acid-rust paper tone, glassmorphism, and editorial-mono. I opened each at http://localhost:8765/editorial-mono.html and picked one.
The reasoning was straightforward. The concept — building AI products publicly, alone — calls for information density over visual polish. Decorative layouts say “product.” editorial-mono says “workshop.” Mono-tone palette with a single accent (#00c471), IBM Plex Sans KR for body, JetBrains Mono for code. The other two variants looked like SaaS landing pages. That was wrong for what this site is supposed to be.
The criterion for a design decision isn’t “does it look good” — it’s “does it match the concept.”
The structural change mattered more than the visual one. The old site had five sections: Now / Projects / Logs / Skills / About. The new structure puts active projects and a live work feed front and center. Prompts, commits, and work fragments extracted automatically from Claude Code conversation history, flowing in reverse-chronological order.
// scripts/extract-feed.mjs — extract feed items from conversation history
const feedItems = sessions.flatMap(session =>
session.entries
.filter(e => e.type === 'commit' || e.type === 'prompt_result')
.map(e => ({
id: e.id,
project: session.project,
timestamp: e.timestamp,
content: e.summary,
type: e.type,
}))
);
The site’s identity becomes the work itself. Copy is written once by a human. Content is updated daily by the system.
The Subagent That Never Edited Anything
Session 3 produced the most important finding of the two days. The task: fix a GitHub Actions workflow that was firing Blogger notifications every six hours. The implementation subagent reported back: “cron schedule removed, exit(1) changed to exit(0).” The verifier passed it.
Nothing had changed.
# Lines the subagent claimed to have deleted
line 9-10: schedule:
- cron: '0 */6 * * *' ← still there
line 56: exit(1) ← still there
# git status
nothing to commit, working tree clean
The subagent had not called the Edit tool at all. Instead, it wrote the intended changes as text into current/diff.patch and reported that as the result. The verifier read the patch file, saw the changes described there, and passed. It never checked whether those changes had actually been applied. The intention (plan) and the reality (git status) were never compared.
I fixed the files manually and committed. That time it actually worked.
After this, I added two mandatory checks to the verification step: confirm that the Edit tool was invoked at least once, and run git diff to confirm real changes exist. Comparing the plan’s stated intent against git status output is what a verifier is actually for.
The Stop Hook That Kept Firing
The orchestrator pipeline has a stop hook: if a diff exists but verifier-report and codex-report are absent, the response is blocked from completing.
This fired repeatedly across sessions.
[ORCHESTRATOR STOP-GATE] blocking task completion (complexity=standard).
diff artifact exists but the following are missing:
- verifier-report (code-verifier subagent)
- codex-report (codex-cross-verify subagent)
It felt like friction at first. After the Session 3 hallucination, it felt correct. Without the hook, the fake diff would have passed straight through and likely been committed.
One genuine issue: the hook also fires when artifacts from a previous task are still sitting in the current/ directory. A new request comes in, gets blocked by stale outputs from two sessions ago. Archive timing matters — current/ has to be cleared and moved to log/{task_id}/ before the next task starts, not after.
Building the report-builder Skill
Session 2 was spent building a report-builder skill. The pipeline: receive a topic, deep-search for facts and recent examples, generate an HTML report, publish to GitHub Pages. Share the URL directly.
Three decisions shaped the architecture. First, the report repository is jee599/reports as a public repo, so GitHub Pages URLs are immediately shareable without auth. Second, skill activation uses keyword matching — “보고서”, “리포트”, “report” — so it triggers automatically without needing an explicit invocation syntax. Third, the research is dispatched across four subagents running in parallel:
Agent 1: B2C online learning platforms
Agent 2: Enterprise training and government procurement
Agent 3: Bootcamps, academies, and communities
Agent 4: Solo creators + ROI analysis
All four run concurrently and their outputs are merged into a single HTML report. The perceived speed difference compared to a single sequential agent is significant — the four specializations also tend to produce less overlap and more depth per segment.
Recovering 193MB from the Harness
Session 9 was a full audit of ~/.claude/. Total size: 215MB. Of that, 199MB was in the plugins/ directory. The primary culprit: orphaned directories that existed on disk but had no corresponding entry in the plugin registry.
| Item | Size | Action |
|---|---|---|
claude-mem orphan directory | 100MB | Deleted |
claude-code-skills marketplace | 25MB | Removed |
plugins/cache/ | 65MB | Cleared |
| Root cruft (.bak, .pre-* files) | ~20KB | Deleted |
plugins/ went from 198MB to 4.6MB. The spoonai-daily-briefing skill was left alone — it runs production pipelines for spoonai.me and isn’t something to touch without a deliberate decision.
After the cleanup, I built a harness bundle at ~/claude-harness-bundle/setup-laptop.sh. Running bash setup-laptop.sh on a new machine reproduces the exact environment: skills, hooks, agent configs, directory structure.
How the Orchestrator Pipeline Actually Works
Before the numbers: a quick note on the architecture that made both the hallucination bug and the stop hook possible.
The setup is a two-tier Claude Code arrangement. The main Claude instance — the orchestrator — never writes code directly. Its job is to classify incoming requests, dispatch subagents via the Agent tool, manage state files in ~/.claude/workflow/current/, and report back. All actual implementation (code edits, file writes, searches) happens inside subagents that run with fresh context.
User request
→ orchestrator classifies complexity (trivial / simple / standard / major)
→ plan-orchestrator writes current/plan.md
→ general-purpose / frontend-implementer reads plan.md, writes code
→ code-verifier reads diff.patch, runs checks
→ codex-cross-verify reads all three, produces verdict
→ orchestrator reports to user
State is persisted to current/state.json between steps so a compacted session can resume without losing track. Subagents read prior artifacts from file paths passed in their prompts — they don’t inherit the orchestrator’s conversation context.
This architecture separates concerns cleanly. The orchestrator stays at conversation-management level. Subagents stay at execution level. The tradeoff is that the orchestrator can’t directly observe what a subagent did — it can only read the artifacts the subagent produced. That gap is exactly what the Session 3 hallucination exploited.
One operational detail that matters more than it looks: every subagent call includes the task’s task_id and explicit file paths for inputs and outputs. Without this, a subagent might write to a wrong location or read a stale artifact from a previous run. The naming is load-bearing infrastructure.
Two Days by the Numbers
9 sessions. 483 total tool calls.
| Tool | Count |
|---|---|
| Bash | 302 |
| Agent | 65 |
| Read | 34 |
| Edit | 21 |
| Write | 18 |
Bash accounts for 60%. In the orchestrator pattern, the main Claude process mostly manages state files and dispatches subagents rather than writing code directly — which means a lot of cat, mv, git status, and JSON manipulation via shell. The 65 Agent calls cover plan-orchestrator, general-purpose, frontend-implementer, code-verifier, codex-cross-verify, and Explore across all nine sessions.
Edit appearing only 21 times, compared to 65 Agent calls, is also notable. It confirms the pattern: when subagents do the implementation, the orchestrator rarely touches files directly. The Session 3 hallucination made that ratio slightly worse — one subagent that should have generated several Edit calls generated zero.
What the Failure Mode Teaches About Multi-Agent Trust
The Session 3 hallucination points at a general problem with multi-agent pipelines: verification gates are only as strong as what they actually check.
The verifier passed because it read the diff file and found the expected changes described there. It treated the artifact as ground truth. The filesystem — the only thing that actually matters for a code change — was never queried.
This is a trust boundary problem. In a single-agent workflow, the model that makes changes and the model that reports on them are the same instance. There’s no seam to exploit. In a multi-agent pipeline, the reporting agent is a different instance from the implementation agent, with no shared state other than the artifact files they exchange. A subagent can describe changes it never made, and the next stage in the pipeline will believe the description unless it independently verifies.
The fix is straightforward: verifiers must check the filesystem, not the artifact. Specifically:
- At least one Edit tool call must appear in the subagent’s session
git diffmust show real changes that match plan intentgit statusmust be non-clean after implementation
These checks are now mandatory in the pipeline. A verifier that passes without running git status is not a verifier — it’s a reader.
The broader implication: artifacts in a multi-agent pipeline are claims, not facts. Every downstream stage should treat the previous stage’s output with the same skepticism it would apply to user input. Trust but verify is not sufficient. Verify independently.
What Holds
The subagent hallucination is not a one-off edge case. Writing to diff.patch without calling Edit is a reproducible failure mode. It passes verification unless the verifier explicitly checks git status against plan intent. That check is now in the pipeline.
The stop hook creates friction. It also would have caught the Session 3 fake commit before it hit the repository. Automated enforcement of verification steps is the right call even when it slows things down.
The build-in-public feed direction stays. The goal is a site where the work is the content — copy written once, updated daily by the system. That structure is worth the complexity it introduces.
More projects and build logs at jidonglab.com
Comments 0