PBC for Claude Code - Rebuild48 vs Rebuild49 Case Study
- Context
- What We Observed
- Where The Time Went
- Root Cause
- The Fix
- Mapping to the Research
- Findings by Variable
- Open Questions From This Case
Context
This page is a worked comparison in the same tradition as pbc-4445. One Darmok scenario ran on consecutive days against sheep-dog-grammar, on branches Rebuild48 (May 23) and Rebuild49 (May 24). The pair-range was wide enough to inspect; the diagnosis traces to the same prompt-template site as the pbc-4445 cases but with a sharper proximate cause — the prompt asks Claude to mine a JaCoCo annotation (title="<test-name>") that the project’s pom does not produce.
| Case | |
|---|---|
| Scenario | This object step definition parameter set doesn't exist validation |
| Rebuild48 commit | 965a81ab |
| Rebuild49 commit | dfa011f1 |
| Green-phase range | 2.0 min (121,761 ms) |
| Files patched | (same — new feature path, no main-code patch in green) |
| Diagnosed cause | prompt — JaCoCo cross-reference asks for data the report does not contain |
The remediation is filed as issue #404: precompute the JaCoCo coverage shortlist inside GreenPhase and have identify.md reference the resulting file rather than scrape per-line title= attributes that don’t exist.
What We Observed
| Rebuild48 (May 23) | Rebuild49 (May 24) | |
|---|---|---|
| Commit | 965a81ab |
dfa011f1 |
| Phase total | 793,422 ms | 1,084,929 ms |
| Red phase | 70,515 ms | 78,639 ms |
| Green phase | 452,852 ms | 574,613 ms |
| Refactor phase | 177,260 ms | 350,078 ms |
Green-phase pair-range = 121,761 ms (2.0 min) on a green baseline that has been running 4–9 min on this scenario across the rebuild series. This study focuses on the green phase only; refactor variance is real but is treated separately.
The two runs ran the same Darmok flow (identify.md → fix.md, one --session-id per phase, auto-compact between them as configured). Both runs succeeded; the test passed in both. Total output-token volume in identify is essentially identical (16,488 vs 16,328) — the variance is not “Claude wrote more text,” it is “Claude ran more tool calls.”
Where The Time Went
The session JSONLs were parsed to extract per-segment counts.
| R48 identify | R49 identify | R48 fix | R49 fix | |
|---|---|---|---|---|
| Wall-clock | 342 s | 436 s (+94 s) | 201 s | 217 s (+16 s) |
| Assistant turns | 75 | 83 | 14 | 21 |
| Thinking blocks | 16 | 17 | 1 | 2 |
| Output tokens | 16,488 | 16,328 | 4,074 | 4,281 |
| Cache-read tokens | 3.55 M | 3.95 M | 0.94 M | 1.51 M |
| Bash calls | 23 | 37 (+14) | 1 | 1 |
| Read | 13 | 9 | 0 | 2 |
| Edit / Write | 0 | 0 | 4 | 4 |
mvn verify duration |
— | — | 20.4 s | 20.7 s |
Essentially all of the gap is in identify, and identify is bash-bound. The mvn verify step in fix was wall-clock-identical (20.4 vs 20.7 s); the +16 s in fix is the 7-turn difference at ~2 s per Claude turn round-trip.
The 10 vs 15 JaCoCo bash calls
Both runs started with the same shape: grep -rl "<scenario>" against target/site/jacoco-with-tests/. Both received (Bash completed with no output) because the full scenario name does not appear in those HTMLs. From there each run made progressively narrower queries to extract test-name annotations.
| # | R48 (10) | R49 (15) |
|---|---|---|
| 1 | grep -rl "<full scenario name>" |
same |
| 2 | grep -rl "parameter set" |
grep -rl "parameter set doesn" |
| 3 | ls jacoco-with-tests/ |
grep -rl "step definition parameter" |
| 4 | grep -rl "step definition parameter"; grep -rl "doesn't exist" |
grep -rl "doesn.t exist" |
| 5 | grep -o 'title="..."' TestStepIssueTypes.java.html |
same |
| 6 | grep -oP 'title="\K..."' TestStepIssueTypes.java.html |
grep -oP 'Tests: [^<]+' TestStepIssueTypes.java.html |
| 7 | grep -n "doesn't exist\|..." TestStepIssueTypes.java.html |
grep -oP '(?<=<span...>)...validation...' |
| 8 | grep -n "...step parameters..." TestObject.java.html |
grep -c "." && head -5 TestStepIssueTypes.java.html |
| 9 | grep -n "...PARAMETER_SET..." TestStepIssueTypes.java.html; ls org.farhan.dsl.issues/ |
grep -n "doesn.t exist\|..." TestObject.java.html |
| 10 | grep -rl "...stepDefinitionParameter..." jacoco-with-tests/ |
grep -rl "step definition parameter\|..." jacoco-with-tests/ |
| 11 | — | grep -n "..." SheepDogBuilder.java.html |
| 12 | — | grep -n "..." SheepDogUtility.java.html |
| 13 | — | ls org.farhan.dsl.issues/ |
| 14 | — | grep -n "..." TestStepIssueDetector.java.html |
| 15 | — | ls org.farhan.dsl.grammar/ \| grep -i "row\|step.*param\|IStep" |
The divergence is at command #6. R48 ran grep -oP 'title="\K[^"]+' (extract title= attribute values), got empty, took the empty result as terminal, and pivoted to keyword grep -n on specific HTMLs. R49 ran grep -oP 'Tests: [^<]+' (a different JaCoCo annotation convention), got empty, tried grep -oP '(?<=<span...>)...' which failed with lookbehind assertion is not fixed length, then ran grep -c "." && head -5 to inspect the file format, then widened to three more .java.html files (SheepDogBuilder, SheepDogUtility, TestStepIssueDetector) before bailing to source-tree search.
R49 spent ~50 s on commands #6–#15 chasing the missing annotation. R48 reached the same fallback in ~12 s.
Root Cause
The title="<test-name>" annotation the prompt instructs Claude to extract does not exist in this project’s JaCoCo reports. Verified by re-running mvn clean verify on sheep-dog-grammar and inspecting a fresh *.java.html:
attribute names present: charset, class, content, encoding, equiv, href,
id, lang, onload, rel, src, type, version, xmlns
Only class="fc" / class="nc" (full/no-coverage span markers) and id="Lxx" (line numbers). No per-line test attribution.
jacoco-sessions.html confirms why: exactly one session is recorded —
Session: ubuntu-client-f6c99eb5
Start: May 24, 2026, 6:22:19 PM
Dump: May 24, 2026, 6:22:25 PM
The pom configures the standard prepare-agent execution + an Ant ReportTask over a single jacoco.exec. Per-test attribution requires multiple sessions (one execution-data file per test, aggregated). The directory is named jacoco-with-tests (aspirational) but the config produces aggregate-only coverage.
The aggregate coverage data is still useful for the prompt’s stated goal — narrowing Claude’s search to the classes the failing test’s family actually touches, rather than reading the whole tree. The available signal is: production classes with non-zero coverage, sorted by which packages the runner exercises. That signal lives in jacoco.xml (line-level <line nr="N" mi=".." ci=".."/> entries inside <sourcefile> blocks) and jacoco.csv (class-level totals).
The Fix
Issue #404 — precompute a coverage shortlist in GreenPhase and reference it from identify.md.
Concretely:
GreenPhaseparsestarget/site/jacoco-with-tests/jacoco.xmlonce before invoking Claude.- Writes
target/darmok/jacoco-shortlist.mdcontaining:- Production classes in the runner-class’s package family, sorted by
covered/missedinstruction ratio, with per-class covered line ranges - Sibling test classes that heavily cover those production classes
- Production classes in the runner-class’s package family, sorted by
identify.mdstep 2 changes from “greptitle=attributes in*.java.html” to “read${darmokDir}/jacoco-shortlist.mdand use the listed classes as the search shortlist.”- The UML/source-tree fallback paths stay in the prompt for the compile-error case where
jacoco.xmlmay be absent.
The change is in the system, not in the worker. The expectation is that the +60–90 s of identify-phase variance attributable to the missing-annotation chase disappears across runs.
Collapsing identify.md + fix.md into one green prompt is not in scope of #404. That decision is deferred until the shortlist’s wall-clock impact has been measured — the two-phase split also serves as a “diagnose-before-edit” guardrail independent of search cost.
Mapping to the Research
| Predicted (pbc-research) | Observed |
|---|---|
| Wide pair-range | green-phase +2.0 min on a 4–9 min baseline |
| Each run individually within in-control band | yes — both within typical green-phase range for this scenario |
| Both runs pass the test | yes |
| Two work-trees differ | not in this case — green produced the same artifacts; the variance is entirely in how long it took to get there |
The signature differs from pbc-4445 in one important way: the pbc-4445 cases had observable artifact differences (different files patched in case 1, different code styles in case 2). The Rebuild48–49 case has no work-tree difference at all — same files, same code. The variance is purely in the path the worker took through the prompt. The signal still fired at 2.0 min on the same prompt step.
This strengthens the pbc-4445 claim rather than weakening it. A range chart that only flagged artifact-differing pairs would miss pure-path variance like this one; the pair-range signal catches it the same way.
Findings by Variable
Each subsection records this run’s findings about one Wheeler variable. Read the same heading across the run sequence to see how our understanding of that variable evolved.
green time pair range
This was a two-run comparison (Rebuild48 vs Rebuild49) with a wide green-phase pair-range of 2.0 min (121,761 ms) on a 4–9 min baseline. The diagnosis was a prompt/producer cause: identify.md instructs Claude to mine a title="<test-name>" JaCoCo annotation that the project’s pom never produces, so each run made progressively narrower bash queries chasing a missing attribute and diverged at command #6 — one run bailed to a source-tree fallback in ~12 s, the other spent ~50 s widening across more .java.html files. Pair-range caught this even though both runs produced identical artifacts (same files, same code); the variance was purely in the path the worker took through the under-specified prompt step. The fix is in the producer (#404): precompute a coverage shortlist from jacoco.xml and have identify.md read it rather than scrape attributes that don’t exist.
green time pair range moving range
No finding this run.
green time
No finding this run.
green time moving range
No finding this run.
scale & green tokens
Total output-token volume in identify was essentially identical between the two runs (16,488 vs 16,328), so the variance was tool-call count, not text generation. The two runs ran on consecutive days at different times, so a server/decode-rate confound was folded into the pair-range here; that confound was only later separated out by token-scaling and concurrent pairing. The prompt/producer cause remains primary for this case.
warm-up position
No finding this run.
Open Questions From This Case
- The pbc-4445 open question about exploration-depth signatures (
Agentsubagent count, distinct files read,xargs grepcross-reference count) holds here too. Rebuild48 and Rebuild49 used no subagents; the divergence is in bash variation depth. A “bash retries on the same target file” counter might be the cheap signal for this prompt-shape case. - After #404 lands, re-running this scenario 2–3 times on the same branch will show whether identify-phase variance narrows. If it does not, the next assignable cause becomes visible (likely: fix-phase Claude-turn-count variance, currently the +16 s minor signal).
- The remaining refactor-phase variance (+173 s in this pair) is out of scope here but worth a parallel study. The post-compact session for R49 ran a full Phase 2 behavioral analysis on a new file (
RowIssueDetector.java); R48 short-circuited at Phase 1 on a structural rename. Different cause site, different prompt — separate investigation.