Real iteration cycles
This series is a record based on the harness environment as it existed at the time of writing (file contents, run logs, file counts). The harness has kept evolving since this series was written, so quoted file contents, counts, and procedures may differ from the current state. The focus of this series is the design ideas (five-facet separation, verification gates, growth discipline) rather than the specific values.
This chapter walks through representative iteration cycles that surfaced after bootstrap of the new harness and were structurally resolved.
Below are the ones selected from ~/.claude/plans/immutable-popping-planet.md §11 (T1-T8 verification residuals) that led to structural fixes.
| # | Iteration | Detection path | Fix |
|---|---|---|---|
| Iteration 1 (§11 #2) | Lasting fix for the commit-gate hook missing subagent transcripts | T2: autodev developer subagent couldn't commit due to hook block | Exclude user content with tool_use_id via jq filter |
| Iteration 2 (§11 #3) | Make per-repo doc-update obligations pointer-tier-vs-real-repo explicit | T2: developer tried to create the pointer tier inside the worktree | Add a clause to autodev SKILL ①/②'/③ + columnize the plan.md template |
| Iteration 3 (§11 #4 + #7) | Resolve doubled worktree for the developer subagent | .claude/worktrees/agent-<id>/ debris with no cleanup path | Remove isolation: worktree from agents/developer.md frontmatter |
| Iteration 4 (§11 #6) | Embed ⓪' assert details in smi-auto-dev SKILL (silent-drop risk) | Static analysis in this session | Verbatim-copy ⓪' from autodev SKILL into smi-auto-dev SKILL |
| Iteration 5 (§11 #11) | Decision: verbatim drift is acceptable | Side-effect of Iteration 4 | Don't add to /harness-prune's 4 detection items; allow independent evolution |
Each in 4 stages.
8.1. Iteration 1: subagent transcript support for the commit-gate hook
Problem found (T2, reverse-string task)
In T2, running the reverse-string task in the autodev flow, the final step of ③ developer spawn (task-branch commit) was blocked at git commit, preventing completion.
The blocking hook was hooks/check-git-commit-gate.sh (PreToolUse / Bash). The old logic:
# Old: get the text of the most recent user turn (last type:"user" message)
LAST_USER_TEXT=$(jq -r '...' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1)
# Does it include a trigger word (commit / push / etc.)?
if echo "$LAST_USER_TEXT" | grep -iqE 'commit|push|...'; then
exit 0
fi
exit 2
This works on a main-agent transcript: the most recent user turn is the human's (the author's) text utterance, and "please commit" contains the trigger word.
But subagent transcripts behave differently. The structure is:
[user] spawn prompt (top of transcript, only 1)
[assistant] tool_use (Bash etc.)
[user] tool_result (= Bash output)
[assistant] tool_use
[user] tool_result
...
Picking the most recent user turn returns tool_result, pushing the spawn-prompt text deep. Even when the spawn prompt contains trigger words ("commit to task branch" etc.), the hook can't find them, so git commit exits 2 the moment a subagent attempts it.
Analysis
The structural issue is that the "most recent user turn" grep behaves differently on main and subagent. On main "most recent user turn = human utterance" holds; on subagent it becomes "tool_result."
Main analysis points:
- A structural inconsistency that recreates subagent context loss on the hook path
- T2 worked around it with
CLAUDE_HOOK_SKIP_GIT_COMMIT_GATE=1, but that's a stopgap - Same issue would recur with future subagent kinds
- 3 fix choices:
- (a) Fix the hook — exclude user content containing
tool_use_idso spawn prompts remain candidates - (b) Change autodev SKILL ③ so "developer does staging only; main agent does the commit"
- (c) Add a whitelist exempting subagents from the trigger-word discipline
- (a) Fix the hook — exclude user content containing
Adopted: (a). Reasons:
- Structural solution on the hook side → no change to SKILL / Policy wording
- Subagent autonomy preserved (no change to autodev flow design)
- Side benefit: cases where "commit" appears in a tool_result content (the old false negative) are also fixed
Fix
The LAST_USER_TEXT extraction logic in hooks/check-git-commit-gate.sh (real source):
# Extract the text of the most recent user-origin message (lenient parse)
# user role mixes (a) human/orchestrator utterance and (b) tool_result.
# In subagent transcripts the last user message is often tool_result, missing
# (a) (the spawn-prompt trigger word becomes invisible). Excluding content
# arrays containing tool_use_id elements (= tool_result) keeps the spawn
# prompt (top-of-transcript user message) as a candidate.
LAST_USER_TEXT=$(
jq -R 'fromjson?' "$TRANSCRIPT_PATH" 2>/dev/null \
| jq -s -r '
[.[]
| select(.type=="user")
| select(
(.message.content | type == "string") or
(.message.content | type == "array" and
([.[] | select(has("tool_use_id"))] | length == 0))
)
] | last
| .message.content
| if type=="string" then .
elif type=="array" then ([.[] | (.text // .content // "")] | join("\n"))
else "" end
' 2>/dev/null
)
The key is the select(has("tool_use_id")) filter:
- Keep only user messages that don't contain
tool_use_id(= tool_result) anywhere in content - main agent: the author's normal utterance (string or pure-text array) remains last
- subagent: tool_results excluded; spawn prompt (top-of-transcript user message) remains
developer.md.bak is kept as a revert safety; the new version is in production.
Verification
hooks/tests/test-check-git-commit-gate.sh got 3 new subagent transcript fixtures:
- S1 (expected PASS): first user (= spawn prompt) has trigger word, then tool_results → passes
- S2 (expected BLOCK): neither spawn prompt nor tool_results have trigger word → blocks
- S3 (expected BLOCK): only the tool_result content has the word "commit" (the old false negative) → blocks
Existing 14 + new 3 = 17/17 PASS.
End-to-end verification was the anagram-check task. state.md history records:
- verification (b) PASS: developer subagent reached autonomous
commit (4651e09e), passing through the
new hook fix
= §11 #2 lasting fix in production
That confirms the subagent's autonomous commit path passes the hook without block in production.
8.2. Iteration 2: column-form doc-update obligations
Problem found (T2, reverse-string task)
In T2, architect's plan.md §7 (doc-update obligations) said "add below CONTEXT.md's 3 existing lines," and developer created CONTEXT.md inside the real repo (/Users/<name>/Dev/autodev-smoke/).
Actually CONTEXT.md exists only in the pointer tier (~/.claude/repos/autodev-smoke/CONTEXT.md); the worktree doesn't have a CONTEXT.md at the base SHA. When the text said "add to CONTEXT.md," developer had no signal whether that meant the pointer tier or real repo, and chose to create one inside the worktree.
The author manually reverted it, and the main agent updated the pointer tier on their behalf — a structural inconsistency.
Analysis
Main analysis points:
- The autodev SKILL doesn't explicitly state "per-repo knowledge (CLAUDE.md / CONTEXT.md / INDEX.md) is pointer-tier and not part of worktree commits"
- Persistent risk of architect / developer confusion between pointer tier and real repo
- The output-contracts/plan.md template lacked a "tier" concept and was free-form
Fix choices:
- (a) Add to autodev SKILL ①/②'/③ a clause: "per-repo knowledge is pointer-tier and not part of worktree commits / doc updates here are delegated by main agent"
- (b) Add column-form doc obligation in the output-contracts/plan.md template
Both adopted.
Fix
Fix (a): 1-clause additions to autodev SKILL ① / ②' / ③:
## ① architect spawn (read-only, fresh)
Inject into the spawn prompt: spec.md + **per-repo knowledge by absolute path**
(`~/.claude/repos/<repo>/CLAUDE.md` policy / `CONTEXT.md` glossary / selected
knowledge — these don't reach via worktree on their own and, being
**pointer-tier = outside worktree, are not developer commit targets**) →
architect produces plan.md
## ②' Compose story.md
... **If doc-update obligations target the pointer tier (`~/.claude/repos/<repo>/CLAUDE.md`
/ `CONTEXT.md` / etc.), state in story that the orchestrator (not developer)
handles it**
## ③ developer spawn (worktree isolation, baseRef=head)
story.md is the sole input → install deps → implement → run pass conditions
inside the worktree → done.md evidence → task-branch commit (**pointer-tier
doc updates are out of scope = orchestrator delegate**)
Fix (b): column-form in output-contracts/plan.md:
## Doc-update obligations
<list: target path + tier (pointer / real repo) + updater (developer /
orchestrator delegate) + verification path in column form. Used by auditor
for matching; transcribed verbatim into story.md by orchestrator>
| Target path | Tier | Updater | Verification path |
| --- | --- | --- | --- |
| ... | pointer / real repo | developer (worktree commit) /
orchestrator (delegate) |
auditor Read / git diff |
The "Tier" column splits pointer / real repo and "Updater" branches developer / orchestrator (delegate) — a 4-column table.
Verification
End-to-end verification on the anagram-check task. state.md history:
- ① architect complete (VERDICT: PASS, column-form ## doc-update
obligations table generated; verification (c) OK)
- ④ auditor complete (VERDICT: PASS, three pillars consistent,
column-table delegated CONTEXT.md correctly
judged as not a doc-update omission)
Verification (c) confirms that architect naturally generates the column-form doc-update obligations table (template-conformant) and auditor uses the column table to judge. Changing one clause in the SKILL changing subagent outputs in lockstep is also evidence that Faceted Prompting's composition rule (preload path) is working.
8.3. Iteration 3: resolve developer subagent's doubled worktree
Problem found (T2 → resolved in T5)
In T2, the developer subagent was spawned. The main agent had manually created git worktree add /tmp/autodev-smoke-reverse-string, but the Agent tool also auto-created /Users/<name>/Dev/autodev-smoke/.claude/worktrees/agent-a0cf210624305e2c8/:
[Manual worktree from autodev SKILL ③]
/tmp/autodev-smoke-reverse-string (baseRef=HEAD, pinned by main agent)
[Auto-created by Agent tool isolation]
/Users/<name>/Dev/autodev-smoke/.claude/worktrees/agent-a0cf210624305e2c8/
(untracked files remain; git worktree remove fails; --force required)
The autodev flow completed despite the doubling, but the latter had no cleanup path — git worktree remove failed with untracked changes, requiring --force. The remnant stayed with its branch (worktree-agent-a0cf210624305e2c8), and monthly /harness-prune didn't include .claude/worktrees/ in scope.
Analysis
The source was isolation: worktree in the agents/developer.md frontmatter:
---
name: developer
description: ...
model: inherit
tools: Read, Write, Edit, Bash
isolation: worktree # ← causes Agent-tool auto-worktree
---
isolation: worktree makes Agent tool auto-create .claude/worktrees/agent-<id>/ at subagent spawn. Meanwhile, autodev SKILL ③ designs for the orchestrator to manually git worktree add /tmp/<repo>-<task> and specify cwd. The two independently create worktrees, resulting in doubling.
Main analysis points:
- §11 #4 (worktree-cleanup-path absence) source identified in §11 #7
- Existing [factorial state.md] also shows the same shape (
task_branch: (deleted) worktree-agent-ad58eb1f2b0ebea55) - Fix choices:
- (a) Remove
isolation: worktreefromdeveloper.mdfrontmatter, consolidating on the manual baseRef=HEAD worktree in autodev SKILL ③ - (b) Or change autodev SKILL ③ to depend on Agent tool isolation: worktree, reverting the manual worktree (contradicts cd-less design; not recommended)
- (a) Remove
(a) adopted. The cd-less design from Phase 1-3 (orchestrator pins worktree_path in state.md and controls it) is more consistent.
Fix
Removed isolation: worktree from agents/developer.md frontmatter. developer.md.bak kept as revert safety.
---
name: developer
description: Implement inside the worktree with story.md as the sole input.
Run pass conditions and record evidence.
model: inherit
tools: Read, Write, Edit, Bash
---
You are a spec-faithful implementer. story.md is your sole primary input;
implement inside the worktree (branched from HEAD).
Cleanup: an orphan worktree from T8 had a complete implementation (fizzbuzz-cli src + test) that would have been lost. It was preserved by committing to a new branch before the worktree was --force removed. fizzbuzz-cli ultimately reached master via rebase + ff merge as commit 8eb7cc4.
Verification
T5 ran the full autodev flow against the palindrome-check spec. No new .claude/worktrees/agent-* appeared; everything consolidated on the manual baseRef=HEAD worktree (/tmp/autodev-smoke-palindrome-check).
anagram-check state.md history:
- verification (a) continued: no new .claude/worktrees/agent-*
The doubled-worktree problem is structurally resolved; the cleanup difficulty disappears with it.
8.4. Iteration 4: verbatim-copy ⓪' from autodev into smi-auto-dev
Problem found (static analysis in this session)
smi-auto-dev SKILL says at the top:
## Quality gates + resume are shared with autodev (self-contained recap)
Spawn chain, VERDICT gating, state.md counters, handoff + ⓪' resume branch
are identical to autodev. ①-⑤ skeleton same. **To avoid silent drop, do not
Read-compose autodev SKILL.md; this skill is self-complete**.
But the ⓪' assert added to autodev SKILL during Phase 1-3 (spec path absolutize / extract <repo> / Read/trim .repo_root / assert dir + git work tree / fallback) was not re-stated in smi-auto-dev's body.
Reading the document at face value, smi-auto-dev cannot share the Phase 1-3 patched ⓪'. "Same as autodev" being interpreted as "main agent reads autodev SKILL" exists as a possibility, but contradicts the discipline "no Read composition; self-complete" — a silent-drop risk.
Analysis
Main points:
- A SKILL declaring silent-drop avoidance harbors the same risk itself
- Bootstrap (the new-harness rollout) likely missed the re-statement on smi-auto-dev's side
- Fix choices:
- (a) verbatim-copy ⓪' assert details from autodev SKILL into smi-auto-dev SKILL.md (consistent with self-completeness; check that ≤80 size cap holds)
- (b) Add a frontmatter "preload autodev SKILL.md at bootstrap" (breaks one-file-one-concern)
- (c) Verify behaviorally only, accepting wording drift (silent-drop risk remains)
(a) adopted. It preserves one-file-one-concern (each SKILL is self-contained) and is expected to stay under the cap.
Fix
Add this section to smi-auto-dev/SKILL.md (verbatim copy from autodev SKILL):
## ⓪' Pre-launch assert + resume judgment (verbatim with autodev)
- realpath the spec-path argument → extract `<repo>` from
`~/.claude/repos/<repo>/...` → Read/trim `~/.claude/repos/<repo>/.repo_root`
→ obtain repo_root (assert: dir exists + `git -C <repo_root> rev-parse
--is-inside-work-tree` true)
- On failure (absolutize fails / `<repo>` extraction fails / `.repo_root`
missing or broken), AskUserQuestion for repo selection
- Read `~/.claude/repos/<repo>/INDEX.md` → Read only task-related knowledge
files on demand
- `git -C <repo_root> rev-parse HEAD` recorded as base SHA in state.md
- **Resume branch (cross-session resilience)**: if handoff dir / state.md
exist, Read → continue from `last_completed_step`
5 bullets, verbatim-copied. Now "no Read-compose; self-complete" actually holds.
Verification
Size check: smi-auto-dev SKILL.md post-fix is 28/80 lines, within cap (no need to borrow autodev's ≤100 exception).
Silent-drop risk resolution: smi-auto-dev SKILL now independently carries the Phase 1-3 patched ⓪', no longer dependent on Read paths.
8.5. Iteration 5: accepting verbatim drift
Problem found (side-effect of Iteration 4)
Iteration 4's verbatim-copy raised a new concern:
"If we verbatim-copy, won't future changes to autodev SKILL's ⓪' require manual sync to smi-auto-dev?"
A reasonable concern. The same wording in 2 places risks divergence if only one is updated — recreating symptom 2 from before the rebuild (the same instruction scattered across multiple facets).
Analysis
Main choices:
- (A) Add verbatim-drift detection to monthly
/harness-prune(auto-detect string diff between autodev ⓪' and smi-auto-dev ⓪') - (B) Give up verbatim copy and switch to frontmatter preload (Iteration 4 choice (b))
- (C) Allow independent evolution — each SKILL can evolve self-contained; don't require verbatim alignment
(A) is technically possible but expands /harness-prune from 4 to 5 detection items, contradicting the discipline (detection fixed; no metrics aggregation).
(B) breaks one-file-one-concern (each SKILL self-contained).
(C) adopted. Reasons:
- Each SKILL's responsibility is independent; autodev and smi-auto-dev are in a derived relationship but not the same
- When ⓪' details change, realistically only one side might be changed (e.g. smi-auto-dev adds a resume bullet because of its longer Phase 0 questioning)
- Paying maintenance cost to prevent silent drift is less useful than allowing independent evolution so each SKILL evolves to its purpose
Fix
Recorded as policy in ~/.claude/plans/immutable-popping-planet.md §11 #11:
**Adopted as-is**: no revert safety (pure addition). autodev SKILL's ⓪' and
this section are currently verbatim, but **may diverge in the future** (each
SKILL allowed to evolve self-contained; verbatim-alignment detection is
**not implemented** in `/harness-prune`'s current 4 detection items =
30-day-zero / size-overrun / orphan / dangling; this session verified by
reading the SKILL.md spec). To prevent silent drift, **new linting** is
needed, but for now (D) independent-evolution allowance is the policy.
This aligns with the discipline "don't add to /harness-prune's detection items."
Verification
Verification is meta-confirmation of the policy decision:
/harness-prune's detection items remain 4 (no feature growth) — verified by reading~/.claude/skills/harness-prune/SKILL.md; at the time of this revision, still 4- smi-auto-dev SKILL.md and autodev SKILL.md's ⓪' are currently string-matched (verbatim) — verified statically
- Future divergence is structurally allowed (not a bug)
This makes "verbatim-copy resolves silent drop at the cost of accepting drift risk" an intentional choice.
8.6. Iteration cycle overall
The iterations covered so far run as a single ratchet:
Key points of the ratchet:
- Bootstrap can't inject a perfect setup — after rollout, silent drops / doubled worktrees / wording drifts always emerge
- Surfaced issues get recorded as §11 residuals, listing 2-3 fix choices for decision
- Adopted fixes are applied in production +
.bakretained (revert safety) and end-to-end verified in other tasks - Recording verification PASS in state.md history is production-environment evidence that the fix worked
Each of these is an independent fix, but they share the trait of completing problem-find → applied within one session. The bootstrap → T1-T4 → fix proposals → T5-T8 → consolidation cycle ran across multiple sessions.
Not aiming for perfection at bootstrap is the core. However carefully the setup is designed in advance, running one full subagent loop reveals silent drops / doubled worktrees / wording drifts.
The discipline of structurally resolving each one with a lasting fix per loop is the main driver getting the new harness to a stable period. The combination of §11 residual list + .bak retention + state.md history works as a tool for "structurally learning from failure."
8.7. Iterations are verifiable in git history
Every iteration covered so far is left behind as an actual commit in ~/.claude/'s git history (verifiable with git -C ~/.claude log --oneline). For example, Iteration 1 (the commit-gate hook) corresponds to a commit titled feat(hooks): git commit 暴発防止 PreToolUse gate + 関連 facet 整備 (the actual commit subject, in Japanese — "add a PreToolUse gate against accidental git commit, plus related facet groundwork"), and that same commit also includes the verification fixture (the added test cases in hooks/tests/test-check-git-commit-gate.sh). This chapter's examples are backed not by "an iteration recounted verbally" but by "an iteration left behind as a diff."
8.8. An example of ongoing iteration
Every iteration covered so far was a lasting fix completed within a single session, from problem-find to production. But iteration doesn't stop there — it keeps happening usage-driven for as long as the harness stays in operation. Here's one example of an iteration that doesn't take the shape of adding a facet (facet addition itself is covered in Chapter 7).
Revising rules/response-links.md: The norm originally assumed that "an outside-workspace path becomes clickable if written as a file:///<absolute path> URI." But checking it hands-on in the Claude Code VSCode extension revealed that this URI form doesn't actually become clickable. The norm was revised to "raw absolute path only; file:///, vscode://file/..., vscode://open?url=... all fail to work," and every reference to ~/.claude/repos/<repo>/... in this series was rewritten to follow the new rule too.
What this example shows is that a norm isn't done the moment it's written — hands-on verification can overturn it. A norm built on an assumption of "this should work" isn't confirmed correct until you actually run it.
8.9. Summary of this chapter
- After bootstrap, silent drops / doubled worktrees / wording drifts surface inevitably. Perfect injection is impossible
- Representative iterations walked through as problem-find → analysis → fix → verification:
- subagent-transcript support for the commit-gate hook (
tool_use_idexclusion filter) - column-form doc obligations (1-clause SKILL addition + 4-column plan.md template)
- developer-doubled-worktree resolution (
isolation: worktreefrontmatter removal) - smi-auto-dev ⓪' verbatim copy (silent-drop risk resolution)
- accepting verbatim drift (don't expand
/harness-prune's detection items)
- subagent-transcript support for the commit-gate hook (
- Iterations that don't take the shape of adding a facet keep happening too (e.g. the
response-links.mdfix, where hands-on verification overturned a norm's assumption) - Iteration cycle = §11 residual list +
.bakretention + state.md history + end-to-end fixture/task - The full toolkit "structurally learn from failure" drives the ratchet
The next chapter wraps up: 3-tier summary / state of the 4 Quality metrics / road ahead.