Imported from jleechanorg/jleechanbrain (
skills/repro/SKILL.md). Install upstream withnpx skills add jleechanorg/jleechanbrain --skill repro. Copyright stays with the author.
name: repro description: Thin pointer — canonical /repro workflow lives in WorldArchitect repo at .claude/skills/repro-twin-clone-evidence/SKILL.md. File gh issue + br create bead, copy campaign, reproduce, verdict. No draft PR. tags: [repro, worldarchitect, firestore, god-mode, directive, lineage, parentage, campaign-state, bq, llm] changelog:
-
4.0.0 (2026-08-04) Drop draft PR gate; add local bead gate (operator directive 2026-08-04).
/reprono longer precreates a draft PR throughgh-safe-publish pr create— the only persistent deliverables at the start of the workflow are now (1) agh-safe-publish issue createrecord onjleechanorg/worldarchitect.aiand (2) a localbr createbead in the repo's.beads/issues.jsonl(type=bug, priority=2 default, source_repo=worldarchitect.ai, with the same body as the issue). The 4-component cluster-triggered prompt-fix PR is still the canonical durable-fix output (perreferences/prompt-fix-deliverable-shape-2026-07-18.md) —/reprojust no longer precreates an empty draft branch ahead of the diagnostic. Bead invocation:br create "<title>" --type bug --priority 2 --description-file <path>(MUSTcdto the repo first —brexits with "Beads not initialized" outside a configured repo; verified by smoke test 2026-08-04 in${HOME}/projects/worldarchitect.ai). Repo-local ID prefix is configured in~/.beads/config.yaml—jleechanorg/worldarchitect.aiuses prefixrev-(formatrev-<hash>); other repos may usebr-/beads-/etc. Bead body MUST carry the same canonical-state contradiction + scene/turn ref + repro recipe as the issue body, so the two records stay coupled. Single-turn flow is now: file gh issue → write bead body file →br create --description-file→ copy_campaign + replay (still in the same session when static-evidence gate is satisfied). Frontmatter description + "Hard gates" section + "Single-turn Gate 1 + Gate 2" subsection renamed to "Gate 1 + bead" + Gate 0 rate-limit matrix trimmed to issue-only + Git-worktree-pitfall reframed + Failure-handling dropped the draft-PR failure row + bead failure row added.references/architecture-decision.mdandreferences/gh-rate-limit-rest-fallback.mdalready cover the issue-only rate-limit/rest-fallback paths and do NOT need updating. -
3.2.0 (2026-07-21) "Non-repro verification recipe for LLM-forgot-NPC-was-dead reports" — verified on jleechanorg/worldarchitect.ai issue #8506 (campaign q04GfOEl4SWnEQrFUVST, scene 189, NOT A REPRO — issue closed with state_reason=not_planned). When the user reports "the LLM forgot / ignored / resurrected NPC X", the report is statistically more often a user misread than a real canonical-state bug. NEW pre-fix gate §0.85 "non-repro candidate check". (a) confirm npc_data..status matches what the user claims via the copied-campaign state.json (NOT the live URL — auth-gated); (b) grep the exported story.txt for action-verb patterns (speaks, whispers, attacks, laughs, nods, casts, walks, stands) attached to the NPC name AFTER the recorded death timestamp — 0 hits means LLM honored the state, >=1 hit means real bug; (c) WATCH FOR RESURRECTION-RITUAL FALSE-POSITIVES — "Speak with Dead", "Speak with Plants", or lore-rooted channels can produce text that references a dead NPC speaking WITHOUT violating canonical state — the canonical-state check must precede the LLM-text grep; (d) NPC-NAME FRAGMENTATION PATTERN — when npc_data contains a bare name AND "name surname" (e.g. Wyll plus Wyll Ravengard) with DIFFERENT status fields, the death-status question is ambiguous — call this out in the diagnostic but DO NOT treat it as the user's bug until the user's specific quote is verified. Cross-skill rule — NEVER ship a prompt-layer "fix" for a canonical-state-anchor class bug without first proving the LLM dishonored canonical state in a verbatim export quote. New reference non-repro-verification-recipe.md captures the full 5-step recipe — copy with --story-max-user-scene-number=N, download state + story, grep npc_data status, grep story action-verbs, write 4-sentence diagnostic comment, close via REST PATCH state_reason=not_planned. Verified outcome on #8506 — full diagnostic ran in about 3 min, issue filed + commented + closed all in one session with zero prompt-layer edits.
-
3.5.0 (2026-07-22) Factor H — prompt-renders-but-unbounded added to god-mode-directive-missing-subclasses.md (verified on PR #8477 review on campaign wc2BBcSgOljiU3vJ160A, 2026-07-22 23:55 UTC). Factor H is the 8th sibling of the directive-pairing-invariant family — structurally distinct from A through G because persistence AND delivery BOTH work. Symptom: god_mode_directives[] grew to 250 entries; build_god_mode_directives_block() at mvp_site/agent_prompts.py line 2351 renders the full array verbatim into the uncached dynamic_instructions channel (33745 chars / 8437 tokens / 5069 words every god-mode turn), with no select_directives_by_budget() mirroring the parallel select_memories_by_budget() mechanism that already budgets core_memories[]. LLM tunes the block out over many turns via lost-in-the-middle plus rule fatigue; user-perceived symptom is "directives never there." Diagnostic: god_mode_directives length > 50 AND rendered block > 16000 chars (approx 4K tokens) confirms Factor H. Fix shape: add select_directives_by_budget() to mvp_site/memory_utils.py mirroring the core_memories pattern, called from build_god_mode_directives_block(). PR #8477 fixes the concurrency race (rev-e0qv9) but does NOT address the user symptom — at 250 entries the race never fires while the bloat fires every turn. Worked example captured in History table.
-
3.2.1 (2026-07-21) "two keys same character different status" pitfall — verified on #8506. npc_data had Wyll (status: missing, entity_id npc_wyll_001) and Wyll Ravengard (status: dead, no entity_id) coexisting in the same npc_data dict because Mizora/Wyll lore got persisted under two separate entity_id roots. The LLM honors both keys correctly — the export shows Wyll Ravengard dying on-screen and the bare Wyll key later narrating only as a missing-person reference. This is a structural data-quality issue, NOT a duplicate-NPC problem and NOT a state-persistence violation. Workaround when surveying npc_data for the user's reported "dead NPC" — search case-insensitively for BOTH the bare name AND the surname-composed form before concluding the state. Symptom — len(npc_data) >= 1 matches for the lowercase bare name, AND >= 1 matches for the lowercase surname-composed form, with at least one status NOT matching the user's claim. Verdict row — STRUCTURAL DATA-QUALITY (two key variants of same character), not HISTORICAL RED ARTIFACT. Future-session rule — when the canonical state shows two keys for the same character with different status, the right next-step is to ADD it to the bug-class-6 taxonomy in references/npc-status-persistence-bug.md (sub-class 7: split-character dual-state), NOT to propose a prompt-layer fix for a missing NPC.
-
3.1.0 (2026-07-21) Latency-class diagnostic — Step 0.76 cross-campaign cache-hit comparison (issue #8501, campaign
q04GfOEl4SWnEQrFUVST). New mandatory pre-§1 step for any latency-class symptom: run a per-campaigncache_hit_pctaggregation in BQ (llm_payloads.cached_tokens / estimated_input_tokens) before grepping code. If the slow campaign's cache_hit_pct is <60% AND a same-class baseline campaign (more story, no reported latency) is >70%, the bug is prompt-cache-invalidation churn, NOT story bloat. Verified worked example:q04GfOEl4SWnEQrFUVST48% cache hit (alternating 0%↔75-147% per turn) vsRMCPAPdfuErh8MgRuj6n(Visenya V8) stable 81.6% across 103 calls. The user's pivot clue "my OTHER campaign with more content didn't have this latency" is the diagnostic key — treat as ground truth and verify. Top 3 cache-buster suspects: (1) god-mode directive inline replay on god-mode turns; (2) long-form narrative module import ingod_mode_directives[]that gets auto-referenced; (3) story-history truncation race causingstory_tokens_estto vary per turn. Latency source of truth =latency_metrics.duration_msandlatency_metrics.ttfc_ms, NOTllm_payloads.latency_ms(column doesn't exist). Full recipe added toreferences/bq-llm-payload-truncation-pitfall.md§"Cross-campaign cache-hit comparison". Cross-skill rule: when the user reports perceived latency, DO NOT start by grepping code for prompt-bloat suspects — start by querying cache_hit_pct. Story-budget is almost never the gating lever. -
3.0.1 (2026-07-21) NEW pitfall: contract-test resolver pointing to the wrong repo (verified PR #8500).
-
3.7.1 (2026-07-23) Step 0.77 worked-end-to-end on the 4th-sibling cluster (issue #8528 → PR #8531 + #8532). Verified on jleechanorg/worldarchitect.ai #8528 (campaign
wc2BBcSgOljiU3vJ160A, scene 454). The user's literal fix proposal — "preserve the first and last 20 god mode entries" — turned out to match the existingselect_memories_by_budget(...)template atmvp_site/memory_utils.py:186exactly. Implementation chose newest-50-first with canonical-state retention (older entries stay incustom_campaign_state.god_mode_directivesfor re-read). PR-A #8531 caps the dynamic-channel capacity to ≤50 directives + adds AST lintscripts/check_god_mode_directive_routing.py; PR-B #8532 detects value-drift instate_updates.npc_data.<NPC>.equipment_bonus. Both fixes shipped from a single cron-driven 20-min follow-up session after the user asked the LLM "did the llm even receive the instruction" — that's the Step 0.77 sequencing working as designed. NEW architecture gap surfaced this session: god-mode responses never get written back to canonical state — onlycore_memories[]get theselect_memories_by_budget()writeback hook; god-modedirectives.addsurvives only as LLM-authored paraphrases incontents[]history. New referencereferences/god-mode-directive-writeback-gap.mddocuments the asymmetry, the BQ detection recipe, and the 2-half durable fix shape (writeback-on-every-god-mode-turn + read-snapshot-into-system_instruction). Pair this withreferences/god-mode-directive-routing-architecture.md(channel-side fix) andreferences/state-update-value-derivation-drift.md(state-update-side fix) — the three refs together cover the routing + persistence + state-update triad, all three required to close the gear-formula bug fully. Lesson encoded for next session: when the user proposes a concrete fix shape that matches an existing code pattern, the existing pattern IS the answer — don't green-field a new design. Trustselect_memories_by_budgetas the template forselect_directives_by_budget. NEW pre-pivot gate for 4th-sibling clusters: when a campaign accumulates 3+ repros, BEFORE asking the user which fix shape to pick, rungit branch -a | grep <issue-number>to detect in-flight fixes already pushed by prior sessions/cron — they often exist as draft PRs you can drive forward instead of starting new PRs. -
3.7.0 (2026-07-23) Step 0.77 — BQ-first diagnostic for directive-loss reports; pre-prompt-fix menu anti-pattern codified. Verified on jleechanorg/worldarchitect.ai issue #8528 (campaign
wc2BBcSgOljiU3vJ160A, 4th sibling). When user reports any "LLM ignored my directive / command forgotten / formula wrong / god-mode correction didn't stick", the FIRST action MUST bebq querypulling bothgemini_provider.stream.request_jsonAND the correspondingGodModeAgent.response_text/StoryModeAgent.response_textfor the relevant turn(s). Verify (a) rule text present in request_json at offset X% of served prompt, (b) LLM wrote narrative correctly, (c) LLM wrote structuredstate_updatescorrectly. Output ONE diagnosis with the three evidence blocks above (offsets, narrative quote, structured-field row). NEVER post a 2-or-3-way menu of fix directions before running this BQ query. User pushback on #8528 was "Read the actual raw LLM request in BQ did the LLM even see the directive for scaling the equipment?" — that pushed back against an agent-posted A/B/C menu. Elevates Factor G revised doctrine (changelog 2.9.0, #8498) from a lore citation into a hard sequence step. Companion pre-correction-turn check: pull 3-6gemini_provider.streamturns BEFORE the user's correction(s); pre-correction baseline at 47.6%-68.6% offset showed the rule was attended but LLM never wrotenpc_data.equipment_bonus— surfaced the new bug sub-class. New referencereferences/state-update-value-derivation-drift.mddocuments the 7th sub-class of thenpc-status-persistence-bugtaxonomy (LLM correctly derives value in narrative + writes the field name + but writes the wrong numeric value, ~50% drift). 4-component durable fix shape recommended (state-update value-derivation block +select_directives_by_budget()+ 12-test contract + CI lintscripts/check_state_update_value_drift.py). Also new BQ output-format pitfall:bq query --format=jsonchokes on control chars in 350KB-caprequest_json(Invalid control character at: line N column M); workaround--format=csvOR redirect stdout to file +json_parse(strict=False). -
3.6.0 (2026-07-23) Factor H lost-in-the-middle sub-class — rule IS delivered but buried at >90% of served prompt (verified on jleechanorg/worldarchitect.ai issue #8526 / draft PR #8527, campaign
wc2BBcSgOljiU3vJ160A, 2026-07-23 ~00:25 UTC). Extension of the Factor H family: the existing Factor H case (god-mode-directive-missing Factor H —god_mode_directives[]array rendered verbatim into dynamic_instructions >16K chars) handles "rule present but LLM can't attend to it." This sub-class handles the SAME failure mode in a DIFFERENT prompt file (canonical prompt files rather than god-mode directives) with a DIFFERENT diagnostic (file position in served prompt rather than rendered-block size). Verified recipe: theTurn 3: MANDATORY - Initialize first companion quest arcrule atmvp_site/prompts/living_world_instruction.md:1071was at offset 291,436 / 292,169 (99.7%) of the served system prompt despite being fully loaded intoStoryModeAgent.REQUIRED_PROMPT_ORDER. The dynamic-injection channelbuild_living_world_instructiononly emitted a 5-line stub with no companion-quest content, so the recency window carried zero mirror of the rule. Empirical proof onwc2BBcSgOljiU3vJ160A: 0/219 character-mode turns wrotestate_updates.custom_campaign_state.next_companion_arc_turn, and 0/219 wrotestate_updates.companion_arcs— despitelast_living_world_turn=169(cadence fired many times). Pitfall added toreferences/prompt-delivery-vs-content-2026-07-20.md(Pitfall 6): "Concluding 'prompt delivery works' because the file is loaded into the served prompt" — Step 3 must report<rule_position_in_request_pct>, not justhas_<rule_header>. If the rule is past the 90% mark of the served prompt, it's buried regardless ofhas_<rule>being true. Fix shape (verified): mirror the operational obligation into the dynamic-injection channel so it lands in the LLM's recency window on every trigger turn. The static file keeps the high-cadence policy context; the dynamic block carries the per-turn obligation echo. Diagnostic Python recipe in the patched Pitfall 6. 3rd-sibling cluster trigger fired onwc2BBcSgOljiU3vJ160A(after #8508 / #8510). Branchfix/companion-quest-cadence-mirror-8526HEADa19e5a8cf1. 2 files / +265/-1. 12-test contract inmvp_site/tests/test_living_world_companion_quest_cadence_8526.py— all green. Cross-campaign: everyStoryModeAgentcampaign had the same bug; fix is canonical. -
2.8.0 (2026-07-21) Factor G — prompt-side default missing added to god-mode-directive-missing-subclasses.md (verified on jleechanorg/worldarchitect.ai issue #8497 + campaign
q04GfOEl4SWnEQrFUVSTturn 31, Sanguine Architecture god-of-murder module from PR #8483). Factor G is the 7th sibling of the directive-pairing-invariant family — distinct from Factor F (F = LLM never writesdirectives.add; G = LLM writesdirectives.addcorrectly but the prompt layer has no default-classifier so user must manually god-mode-reinforce every campaign that imports a multi-aspect mechanic). Diagnostic: run thephenotype-lockcode-symbol grep acrossmvp_site/prompts/for the mechanic's vocabulary; 0 hits confirms G. Fix shape (mirrors PR #8491, 2026-07-20): add a "Default Aspect Classifier" § to the relevant prompt file + worked example ingod_mode_instruction.mddirectives table + contract test pinning the default. Verified on campaignq04GfOEl4SWnEQrFUVSTturn 31 (2026-07-21 05:53:11 UTC): LLM used Aspect II (Chitinous Ruin / obsidian wings) when narrating Slayer Form; user god-mode correction at 05:54:02 UTC produced a workingdirectives.add; turn 32 at 06:01:44 UTC honored the directive. So persistence works — the gap is purely prompt-side. Two new pitfalls added to references/bq-llm-payload-truncation-pitfall.md: (1) scene-number-vs-turn_index — the user-reported scene number (e.g. "scene 77") often does NOT correspond to anyturn_indexin BQ. Always confirm withSELECT MAX(turn_index) ... WHERE campaign_id='<CID>'before assuming. (2)turnvsturn_indexcolumn name — the column isturn_index(NOTturn);SELECT ... WHERE turn = 31returns400 Unrecognized name: turn at [2:12]error. Useturn_indexdirectly. -
2.9.0 (2026-07-21) Factor G revised — the LLM-already-received-the-rule branch is NOT a prompt-rule fix (verified on PR #8498). User pushback on the original 2.8.0 fix shape: "are you stupid? ... The fucking fix isn't to hardcode slayer form into the prompt it's to fucking investigate the BQ LLM raw requests like I asked and see if the llm even received the instruction". The BQ raw-request inspection at
2026-07-21 05:53:10 UTCshowed the LLM DID receive the Mantle of the Radiant Slayer §5 + the prior god-mode "Transcendent Beauty confirmed" directive (offsets 25249–33400 + 139038–140903). The drift was architectural (LLM free to ignore 350KB-deep rule on any given turn), not instructional. New mandatory pre-fix diagnostic added to Factor G reference: before proposing ANY prompt-layer fix for Factor G, pull the BQ raw-request for the buggy turn and grep it for the mechanic's vocabulary + the user's directive text. If both appear in the request, the LLM already received the rule — reclassify as "instruction drift / architectural coupling" and the fix is policy + lint + tests, NOT a hardcoded prompt rule. The shipped fix (PR #8498) added: a BANNED anti-pattern in repo-rootCLAUDE.md(campaign-agnostic prompts) + a newmvp_site/prompts/CLAUDE.mdrule file with banned-name list +scripts/check_prompt_agnosticism.pyCI lint + 8 contract tests inmvp_site/tests/test_prompt_agnosticism_8497.py. +492/-0 across 4 files. The reference filereferences/god-mode-directive-factor-g-prompt-default-missing.mdwas rewritten to reflect the rejected fix shape and the new diagnostic discipline. Cross-skill rule (applies to ANY skill that proposes a prompt-layer fix): verify the LLM actually received the rule via BQ before proposing "add the rule to the prompt file." If the LLM DID receive it, the fix is elsewhere. -
3.0.0 (2026-07-21) NPC Peer-Autonomy — 4th canonical-state-anchor sub-class added (issue #8499, PR #8500, campaign
q04GfOEl4SWnEQrFUVST). The 3rd-sibling cluster trigger fired on this campaign (#8490 Factor F + #8497 Factor G + #8499). User symptom: planning block emitted "Send Astarion ahead to prepare the city" — an imperative premise that presupposes dispatch authority the player does not have over an Independent Ally. The narrative mirror had the "Independent Allies / Peer Autonomy / Allied Peerage" doctrine in 8+ places (export lines 2178, 2452, 2498, 2523, 2652, 2932), but the planning layer's## Canonical-State Anchorsection covered only §4 NPC Co-Presence / §5 God-Mode Directive Compliance / §6 NPC Reachability / §7 NPC Status Alignment. §8 NPC Peer-Autonomy was missing. Fix shape (4-component, verified): new### 8. NPC Peer-Autonomy (No "Send" / "Dispatch" / "Order" Framing for Independent Allies)inplanning_protocol.mdwith a 3-class relationship table (Direct / Independent Ally / Antagonist) + forbidden premise forms (Send / Dispatch / Order / Have / Command) + valid rewrites (Negotiate with / Coax / Concede to / Offer) + worked example using the Astarion case + Quick Self-Audit §7; mirror### 9. NPC Peer-Autonomyinnarrative_system_instruction.mdforbidding the narrative-side "follows you" / "travels with you" / "at your side" framing forindependent_allyNPCs; 6-test contract inmvp_site/tests/test_planning_block_npc_peer_autonomy_anchor_8499.py. 3 files changed, 186 insertions(+), 1 deletion(-). 6/6 tests green on the worktree. Branchfix/astarion-independent-ally-planning-anchor-8499HEADb859ac121a. New referencereferences/repro-npc-peer-autonomy-anchor-2026-07-21.mdcaptures the full recipe. -
3.0.1 (2026-07-21) NEW pitfall: contract-test resolver pointing to the wrong repo (verified PR #8500). A contract test that reads prompt files MUST resolve the repo root from
__file__(walking up to the marker file), NOT from a hard-coded constant. First run of the 6-test contract on PR #8500 hard-codedREPO_ROOT = "${HOME}/projects/worldarchitect.ai"(the main checkout). The worktree at${HOME}/projects/wt-astarion-anchor-8501/had the patched prompt files but the test was reading from the main checkout's un-patched files — all 6 tests failed with section-extraction errors, not assertion errors. The agent wasted ~3 turns before spotting the cause. Fix: walk up from__file__untilmvp_site/prompts/planning_protocol.mdexists; CI can pin viaHERMES_REPO_ROOTenv var. See the new "Contract-test resolver pitfall" subsection below. -
3.8.0 (2026-07-28) Dropped-Thread repro recovery recipe -- parallel static-evidence inside a fresh session that opens with the user asking why the message was missed. Verified on C0BDEAJH8PK 1785197466.704939 (campaign FsiyESY987DF2lfgolCI, /repro, missed ~6h). New reference references/dropped-repro-parallel-evidence-2026-07-28.md captures the full recipe. Pairs with dropped-messages skill section added same date and SOUL.md ms-on-new-task audit discipline.
-
3.9.0 (2026-07-30) Gemini model code-execution allowlist mismatch — new bug class. Verified on jleechanorg/worldarchitect.ai issue #8673 (bead
rev-gemini-flash-lite-36-code-exec-loop-b7ka1). When a model is advertised as code-execution-capable but the actual API behavior diverges, there are TWO distinct failure modes: (1) allowlist gap (model not inMODELS_WITH_CODE_EXECUTION→ silent fallback to native two-phase), (2) infinite loop on tool use (finish_reason: TOO_MANY_TOOL_CALLS, 40-50 code parts, 20-27k tokens, no output). PR #8512's "inherits 3.x code execution" comment was doubly wrong for bothgemini-3.5-flash-liteANDgemini-3.6-flash— neither supports code-execution at the API level (verified live with GCP secret-managergemini-api-key). New mandatory diagnostic step: before adding any model to a tool-use allowlist, run an API probe withcode_executiontool +temp=0.7and checkfinish_reason+code_partscount to detect infinite-loop models. Static allowlist reads CANNOT detect mode 2 — both modes require the live API probe. New referencereferences/gemini-model-code-exec-allowlist-mismatch-2026-07-30.mddocuments the bug class signature, the diagnostic recipe, and the verified finding. Pairs withreferences/prompt-delivery-vs-content-2026-07-20.md— both classes share the pattern "code-level check passes, behavior-level check fails." -
2.5.0 (2026-07-21) Added references/bq-llm-payload-truncation-pitfall.md
-
2.3.0 (2026-07-19)
-
2.2.0 (2026-07-18) Cross-campaign Bug Class 4 cluster extension — verified on jleechanorg/worldarchitect.ai issue #8451 / draft PR #8452 (branch
fix/sariel-171-magic-sensor-8451, HEAD4b991097bf). Second confirmed cross-campaign instance of LLM-invented lore artifact ("Vaelaros-tuned Blood-Scent focus" on campaignCg2m2TkGFFez7XBynEahat scene 171, three-running retcons on the same campaign). Three new lessons: (1) Cross-campaign cluster trigger — when the SAME magic-detection trope signature appears on ≥2 differentcampaign_ids, the structural issue is in the prompt layer (re-invention at emit time), not per-scene; (2) Persistence-into-state — direct Firestore pre-state read showsGardener Inquisitor(an LLM-invented NPC) was written intonpc_dataandBlood-Scent/Reaver-Hound/Ghost-Huntertokens appear in the game-state document — invention prose is structural, not just streamed; (3)custom_state_keys == []diagnostic — whencustom_stateis empty, the user has no architectural surface to enforce per-campaign "no-magic-detection" rules; every turn the LLM re-derives freely. Seereferences/repro-llm-invented-lore-artifacts-2026-07-18.md§9-§12 for the durable-fix recommendations and the new cross-campaign triggered-prompt-fix recipe. Bug-class-4 trigger now has two durable-fix anchors: the existing NPC Development section innarrative_system_instruction.md(PR #8443) PLUS a new recommended §"Forbidden Invention Class" section enumerating 5 invention patterns (X-tunedmodifier / glowing vial / frequency-sensitive ward / frequency-shield / cross-tier magic detection). -
2.1.0 (2026-07-18) New reference
references/prompt-fix-deliverable-shape-2026-07-18.md— the durable-fix recipe for planning_block canonical-state-anchor violations, verified on jleechanorg/worldarchitect.ai PR #8446 (SHA 4524525569692db3e5a5b36a818a7248b65fae09). Contains: (a) the 4-component deliverable shape — §"Canonical-State Anchor" section in planning_protocol.md + mirror §"Narrative Consistency Anchors" in narrative_system_instruction.md + parallel test file pinning the contract + PR body with coverage map; (b) the cluster-signal-driven decision tree (3rd sibling triggers the bead, 5th sibling triggers the prompt-fix PR); (c) 5 pitfalls from the actual PR-#8446 session including the worktree-silent-edit trap (edits landing in the main checkout because the worktree had no./venv/), the case-fold + section-scope test pattern, and pre-existing-test-failure isolation. Load this BEFORE writing the prompt-fix PR for any sibling in the canonical-state-anchor class. -
2.1.1 (2026-07-18) New pitfall (verified #8451): scene-counter offset trap. The live UI scene number reported by the user (e.g. "scene 171") does NOT match the
SCENE Nmarkers indownload_campaign.pyexports — there is an offset of (live scenes − export scenes). The export goes only up to the moment of capture, and the live UI keeps incrementing. In the #8451 case, the user's scene 171 corresponded to export "SCENE 78" (offset 93). Pitfall: grepping forSCENE 171in an export finds nothing; grepping forSCENE 78finds the right prose. Workaround: capture the export AGAIN immediately before forensic grep — the new export will have SCENE 171 with the live-ui scene number matching. Or: trust the timestamp → scene mapping viadownload_campaign.pylog output (entries 1..N → timestamps). -
2.1.2 (2026-07-18) New pitfall (verified #8451):
__pycache__false-positive in static-evidence greps. The canonical skill's grep recipe inreferences/phenotype-lock-static-evidence.mdsays "Code-symbol grep:grep -rin '<artifact>' mvp_site/" — but aBlood-Scenttest fixture from a prior repro (#8444) lives inmvp_site/tests/__pycache__/test_planning_block_canonical_state_anchor_8444.cpython-312-pytest-9.0.3.pyc. The grep WILL match the.pycand report a false positive — making the agent think the artifact is canon-anchored when it's only present in a compiled test fixture. Workaround: always pass--include='*.py' --include='*.md'(or equivalent) to exclude.pycfiles; OR explicitly note in the PR body "the only matches are in__pycache__/test fixtures, which are stale build artifacts, not canonical sources." -
2.0.0 (2026-07-18) Extended
references/repro-planning-block-and-campaign-cluster-2026-07-18.mdwith the 5-anchor taxonomy for planning_block canonical-state violations: (a) god_mode_directives[] retcons, (b) NPC co-presence / location-reach (NEW sub-class from #8444 / PR #8445 — "Rejoin the Host" choice assumed Aegon at Mander mouth while he was co-present at Highgarden), (c) future-event gates, (d) canonical milestones/inventory, (e) level-up rewards. Recommended durable fix shape is now a single "Choice Premise Validation" section inmvp_site/prompts/planning_protocol.mdcovering all 5 anchors (vs prior §6 which covered only anchor (a)). Added §5.1 NPC co-presence diagnostic steps and §5.5 three newcopy_campaign.py/ Firestore REST gotchas:--allow-same-userflag for cross-UID copies (script bails silently without it),orderBy=createTime descREST failure (use paginated + client-side sort instead),CONTAINSoperator not supported on structuredQuery. Extendedreferences/phenotype-lock-static-evidence.mdwith 5th verdict rowHISTORICAL RED ARTIFACT — NPC co-presence violation+ worked example for #8444. Verified worked example: campaignD3iZvnGiBl9wyveQBFj9"Aegon is with me" (issue #8444, PR #8445) — 5th sibling on this campaign, cluster trigger exceeded by 2×. -
1.7.0 (2026-07-18) Added Gate 0 — pre-flight rate-limit check before invoking
gh-safe-publish(verified on #8438/#8439). The safe-publish wrapper callsgh issue create/gh pr createunder the hood, which use GraphQL — when GraphQL budget is exhausted,gh-safe-publishfails identically to rawgh. Decision matrix added: >500 proceed normally, 1-500 expect 1 retry max, 0 → REST fallback immediately. Extendedreferences/phenotype-lock-static-evidence.mdwith bug-class → verdict-row mapping table (LLM-prose invention case added —HISTORICAL RED ARTIFACT — LLM-prose invention). Extendedreferences/static-evidence-sufficient-no-live-turn.mdwith 4th static signal for LLM-prose inventions (no canonical anchor in code OR prompts; artifact exists only in narrative text). Verified worked example: campaignD3iZvnGiBl9wyveQBFj9"Blood-Scent focus" silver vial (issue #8438, PR #8439). -
1.9.0 (2026-07-18) Added
references/repro-llm-invented-lore-artifacts-2026-07-18.md— captures the durable fix for bug class 4 (LLM invents lore artifacts not in any canon source, especially in low-magic settings) shipped as PR #8443 on campaignD3iZvnGiBl9wyveQBFj9. Includes: bug-class signature (5 invention patterns); bypass-Flask-SSE replay technique (agy --printfor >100K-token prompts that exceed the 320s Flask SSE timeout); three-replay red/green/control proof pattern; durable fix shape fornarrative_system_instruction.mdNPC Development section; PR body template; sibling-cluster context (3rd repro onD3iZvnGiBl9wyveQBFj9exhausted the campaign-cluster signal). -
1.6.0 (2026-07-12) Added references/json-serialization-leak.md — NEW bug class: TypeError on
set/_Sentinelleaking throughget_campaignresponse_data, causing intermittent HTTP 500s (verified from issue #8353, 3 sibling campaigns). Extended references/auth-gate-fallback-repro.md with "Step 6 — Skip headless Chrome, query GCP Cloud Logging" — when the user can load some campaigns but not others, auth bypass is the wrong tool; pull the traceback from Cloud Logging filtered by campaign_id. -
1.3.0 (2026-07-09) Added references/phenotype-lock-static-evidence.md — the 3 static-evidence greps (code-symbol, prior-export, sibling-issue) that should run BEFORE asking the user phenotype-anchor questions. Often answers your own question and prevents a NON-REPRO replay. Added sibling-campaign structural-issue flag (≥3 open repros on same campaign ID → link siblings in issue/PR body, note same root-cause class). Verified from issue #8293 (3rd repro on xK3fp5XrV24oarIINTF7 after #8275/#8277).
-
1.4.0 (2026-07-10) Added references/god-mode-grant-mechanic-not-operationalized.md — a NEW sibling class distinct from god-mode-directive-missing. Custom features (e.g. "The Conqueror's Insight") are granted in narrative text by the God-Mode agent but never written as structured schema (trigger_check_types / formula / dice_expression). The LLM re-derives the mechanic from prose every turn. Diagnostic covers the 2-row pattern in BQ (stream_story_with_game_state = parsed user-msg only; gameplay_streaming = 350KB full payload), the features[] plain-string shape, and the "mechanic ambiguity" variant (ADDED vs REROLL). Verified from campaign xK3fp5XrV24oarIINTF7 issue #8320 / PR #8321 (6th repro on same campaign).
-
1.5.0 (2026-07-12) Shipped Option D fix shape (3-clause preamble: Canonical NPC Status anchor + Narrative Revival blocker + Wrong-Key Death Writes) merged into
references/npc-status-persistence-bug.md. Replaced the prior "Proposed Options A / A' / B / C" block — none of which had shipped — with the actual landed fix (PR #8352, branchfix/npc-status-canonical-anchor-8335@ a7b240f69956). Added the campaign-cluster structural trigger: when ≥3 sibling repros accumulate on the same campaign_id, STOP filing per-scene issues and branch a fresh worktree for a root-cause-first prompt fix that addresses the common anchor layer. Tracks all 8 sibling instances on xK3fp5XrV24oarIINTF7 in 9 days. The non-overlapping sub-classes across siblings 3–8 prove the root cause is prompt-layer, not per-scene. -
1.2.0 (2026-07-08) Added references/two-pronged-render-and-persist-bug.md — the "one symptom, two independent root causes" debugging pattern (render-side field stripping + persist-side save-drop, both required for the user-visible symptom). Verified from issue #8283.
-
1.1.0 (2026-07-08) Added references/find-new-campaign-id-after-copy.md and references/static-evidence-sufficient-no-live-turn.md. The campaign ID after copy_campaign.py isn't in the JSON return — use Firestore REST API directly (the find-by-title path times out on 2900+ campaign accounts).
-
2.5.0 (2026-07-21) Added references/bq-llm-payload-truncation-pitfall.md — the worldarchitecture-ai llm_forensics llm_payloads request_json column is uniformly capped at ~350KB regardless of actual payload size. Filtering by REGEXP_CONTAINS on CAST request_json AS STRING returns 0 hits even when the campaign is there; always filter by the top-level campaign_id column instead. Verified on the 2026-07-21 char creation big prompt repro where the failure was client-side (no LLM call ever fired), not a payload-size limit. Also extended references/auth-gate-fallback-repro.md Pitfalls with two new entries. First, local Flask test-UID mismatch — copy_campaign.py creates the copy under a specific UID but the MCP harness auto-generates a different test UID that cannot see the copied campaign. Drive the test via curl against the local Flask server with X-Test-User-Id set to the copied campaign owner UID, not through the harness. Second, MCP_TEST_MODE=real and MOCK_SERVICES_MODE=false are required for end-to-end LLM-call repros — missing either silently short-circuits the LLM call to a mock.
/repro (Hermes pointer)
This skill is a thin pointer. The canonical source of truth is:
${HOME}/projects/worldarchitect.ai/.claude/skills/repro-twin-clone-evidence/SKILL.md
Always read and execute the canonical skill. Do not duplicate logic here.
When this skill is used
- A Hermes session (Slack, cron, gateway) receives a
/reprorequest - The WorldArchitect repo is available at the path above
Firestore credential requirements
All WorldArchitect scripts (copy_campaign.py, download_campaign.py, etc.) require:
GOOGLE_APPLICATION_CREDENTIALS="$HOME/serviceAccountKey.json"
WORLDAI_GOOGLE_APPLICATION_CREDENTIALS="$HOME/serviceAccountKey.json"
WORLDAI_DEV_MODE=true
WORLDAI_DEV_MODE=true is mandatory — scripts raise ValueError without it.
Hard gates (MUST execute, in order, before anything else)
1. ~/.smartclaw/scripts/gh-safe-publish issue create — File the GitHub issue on jleechanorg/worldarchitect.ai through the outbound secret gate. No env setup, no scripts, no copying until this succeeds. See canonical skill Step 0 for the exact command + template, replacing a raw gh issue create invocation with the gated wrapper.
2. br create (local bead) — File a local bead in the repo's .beads/issues.jsonl carrying the SAME canonical-state contradiction, scene/turn ref, and repro recipe as the issue body. Use --description-file <path> so multi-paragraph markdown survives shell quoting. Default type=bug, priority=2 (override only if user explicitly requests otherwise). source_repo defaults to the current repo via br's auto-detection; do NOT set --assignee (let the user triage). Repo-local ID prefix is configured in ~/.beads/config.yaml (verified in jleechanorg/worldarchitect.ai: prefix=rev-, format rev-<hash>); other repos may use br-, beads-, or whatever prefix that repo configured. The --slug <slug> flag is optional and produces a human-readable slug embedded in the generated ID (e.g. br create --slug survey-my-thing ... → br-survey-my-thing-<hash>). Always cd to the repo before running br create — br exits with Beads not initialized: run 'br init' first outside a configured repo.
Both gates must complete before proceeding to Step 0.75 (bug phenotype capture) or any other step. No draft PR is filed — /repro is no longer coupled to gh pr create. The 4-component cluster-triggered prompt-fix PR (references/prompt-fix-deliverable-shape-2026-07-18.md) is still the canonical durable-fix output, but it ships later from the worktree only when the bug class warrants a prompt-fix PR (3rd-sibling cluster trigger).
Single-turn Gate 1 + Gate 2 flow (verified 2026-08-04, replaces prior #8468 + #8469 worked example)
When the bug class is clear from the user's message AND the canonical-state
contradictions can be enumerated from Firestore reads without a live LLM
turn (per references/static-evidence-sufficient-no-live-turn.md), both
gates can land in the SAME session — without violating the gate contract.
The durable-state guarantee is on origin (issue record) + .beads/issues.jsonl
(bead record), not on whether gates 1 and 2 happened in one session or two.
Sequence (verified worked example: bead rev-<hash> + issue #NNNN, campaign <CID>):
- Write the issue body to
~/.smartclaw/wa-repro-<issue-no>/issue-body.md(NOT/tmp/...—execute_codeis sandbox-scoped per call). - Gate the body:
python3 ~/.smartclaw/lib/outbound_secret_gate.py check --file <path>. - REST-POST the issue:
urllib.requestagainsthttps://api.github.com/repos/jleechanorg/worldarchitect.ai/issues(REST fallback when GraphQL quota is exhausted; same gate applies). - Copy the SAME body to
~/.smartclaw/wa-repro-<bead-id>/bead-body.mdso the bead carries the same canonical-state contradiction + scene/turn ref + repro recipe. Beads and issues stay coupled one-to-one. cdto the worldarchitect.ai repo and runbr create "<title>" --type bug --priority 2 --description-file ~/.smartclaw/wa-repro-<bead-id>/bead-body.md(default priority 2 = P2/bug; only override if user explicitly requests).br createprints the assigned ID on stdout — capture it for step 7.- Continue to Step 0.75 (bug phenotype capture) or — if static-evidence gate
is satisfied — directly to
scripts/copy_campaign.py+ replay. - Post the consolidated reply in Slack with BOTH the issue URL AND the bead
ID (e.g.
[#NNNN](https://github.com/jleechanorg/worldarchitect.ai/issues/NNNN)- bead
rev-<hash>— both IDs are durable records so the user can cross-link from either side).
- bead
Why this works: the gate contract requires the issue be filed BEFORE
any other work (so the bug has a permanent record on origin) AND the
bead be filed BEFORE any other work (so the local tracker records the
class/cluster linkage). Neither requires a draft PR — /repro no longer
precreates an empty branch ahead of the diagnostic. The durable-fix PR
(when warranted) ships later from the worktree via gh-safe-publish pr create
with the bead body linked from the PR description.
Gate 0 — pre-flight rate-limit check (added 2026-07-18, #8438)
Before invoking gh-safe-publish issue create (which calls gh issue create under the hood), check the GraphQL rate-limit budget first:
gh api rate_limit --jq '{graphql: .resources.graphql.remaining, graphql_reset: .resources.graphql.reset, core: .resources.core.remaining}'
Decision matrix:
graphql.remaining |
Action |
|---|---|
> 500 |
Proceed normally with gh-safe-publish |
1..500 |
Proceed but expect at most 1 retry — if first gate fails, jump straight to REST fallback (do NOT retry gh issue create) |
0 |
REST fallback immediately — gh-safe-publish will fail identically. Use references/gh-rate-limit-rest-fallback.md recipe. |
Why: gh-safe-publish issue create calls gh issue create under the hood (the wrapper used to also wrap gh pr create, but since 4.0.0 the wrapper is issue-only). When the GraphQL budget is 0, gh-safe-publish fails identically to raw gh — the safe-publish wrapper does NOT bypass the rate limit. The REST API has a separate quota (core.remaining); when GraphQL is exhausted, REST is usually fresh and the fallback recipe works. Verified 2026-07-18 on #8438/#8439: GraphQL=1086/5000 → REST succeeded, REST=4909/5000 → REST succeeded on first attempt.
Anti-pattern: retrying gh-safe-publish (or gh issue create) when GraphQL shows 0 remaining — every retry consumes more of the same exhausted budget and extends the ETA. Go to REST on the first sign of rate limit.
Step 0.77 — BQ-first diagnostic for directive-loss reports (added 2026-07-23, #8528)
Trigger: User reports any of:
- "LLM ignored my directive" / "LLM didn't apply my rule"
- "Rule was forgotten" / "command forgotten" / "player command ignored"
- "Formula was wrong" / "calculation drifted" / "values off"
- "God-mode correction didn't stick" / "directive didn't take"
- "Reasoning looked fine but state is wrong" (often narratively-correct-but-state-wrong, see sub-class below)
Mandatory first action — BEFORE asking any clarifying question, BEFORE proposing any fix direction:
SELECT FORMAT_TIMESTAMP('%Y-%m-%dT%H:%M:%SZ', ingested_at) AS ts,
agent, turn_index,
LENGTH(request_json) AS req_bytes,
response_text,
request_json
FROM `worldarchitecture-ai.llm_forensics.llm_payloads`
WHERE campaign_id = '<CAMPAIGN_ID>'
AND agent IN ('GodModeAgent','gemini_provider.stream','StoryModeAgent')
AND ingested_at BETWEEN TIMESTAMP('<BEFORE_TS>') AND TIMESTAMP('<AFTER_TS>')
ORDER BY ingested_at ASC
Three checks the diagnostic must answer (with verified worked example offsets):
- Did the LLM receive the rule? Grep
request_jsonfor the user's directive text + the mechanic's vocabulary. Report offset and% into served prompt(lost-in-the-middle threshold ≈85%; verified #8528: rule at 47.6%–68.6% was attended, rule at 89.7%–97.6% was lost). - Did the LLM apply the rule in narrative? Grep
response_textfor narrative acknowledgment (e.g. "applying the canonical (Level / 10) gear formula to their Original Divine Levels"). - Did the LLM write structured state correctly? For each
state_updates.npc_data.<NPC>write, verify the derived value (e.g.equipment_bonus) is consistent with the canonical lookup-table within ±0 (e.g.floor(95/10)=9for Ao L95, not 2).
Output rule: Post ONE diagnosis with the three evidence blocks above (offsets, narrative quote, structured-field row). NEVER post a 2-or-3-way menu of fix directions before running this diagnostic.
Anti-pattern codified (anti-feedback signal): When bug class is "directive-loss", asking "should I fix via prompt rephrasing vs directive budget vs response trimming?" before running the BQ query wastes the user's turn AND misroutes the diagnosis. The BQ data usually answers the question independently. Quote from #8528 user pushback: "Read the actual raw LLM request in BQ did the LLM even see the directive for scaling the equipment?" — directly targeted a clarifying menu the agent had posted.
Companion check — pre-correction turns: When the user reports a current-turn bug, also pull 3–6 gemini_provider.stream turns BEFORE the user's correction(s). Verified pattern: user issued 4 corrections in 6 minutes; the failing state originated 2–7 hours earlier in pre-correction turns where the rule was at offset 47.6% but never written as a structured value. See references/state-update-value-derivation-drift.md for the verified worked example.
Cross-reference: Factor G revised doctrine (changelog 2.9.0, #8498) — "verify the LLM actually received the rule via BQ before proposing 'add the rule to the prompt file.'" Step 0.77 elevates this from a lore citation into a hard sequence step in the workflow.
BQ output-format pitfall: bq query --format=json chokes on control chars in 350KB-cap request_json (json.decoder.JSONDecodeError: Invalid control character at: line 1 column N). Workaround: --format=csv (escapes properly; may truncate extreme string lengths) OR redirect stdout to file (> /tmp/x.json) and parse the file with json_parse(strict=False).
When the diagnostic surfaces a new bug class: load references/state-update-value-derivation-drift.md (verified on wc2BBcSgOljiU3vJ160A, scene 454, 2026-07-23 — LLM correctly applies the formula in narrative + writes npc_data.equipment_bonus field + but writes the wrong value, ~50% of derivation drift).
Bug phenotype capture (Step 0.75)
After gates are confirmed, capture the structured bug phenotype from the user's description before running any repro scripts. Ask targeted clarification questions if the description is vague. See canonical skill Step 0.75.
Before asking the user, run 3 static-evidence greps — they often answer your own question and prevent a NON-REPRO replay. See references/phenotype-lock-static-evidence.md for the full recipe:
- Code-symbol grep —
grep -rn "<bug_token>" mvp_site/ --include="*.py"and…/mvp_site/prompts/. If the user-reported name (e.g. "hidden gold") matches NO symbol in code, the bug is natural-language LLM prose, not a canonical game-state field. This narrows the bug class to stale-context / prompt-side injection BEFORE the first LLM call. - Prior-export grep —
grep -rin "<bug_token>" /tmp/worldarchitect.ai/repro-exports/<campaign_id>-scene*/(plus any sibling repro's export dir). If a prior repro already touched this campaign, the token's presence/absence in those exports bounds when the bug was introduced. - Sibling-issue scan —
gh issue list --repo jleechanorg/worldarchitect.ai --state open --search "<bug_token> OR <scene_number>". If this is the 3rd+ repro on the same campaign ID, flag the campaign as having a structural issue in the issue body AND the matching bead body — it's not just a per-scene bug.
Only ask the user the 3 phenotype anchors (find-scene, literal-block-text, last-input) if all 3 greps leave the bug class genuinely ambiguous. Every clarification question you don't need to ask is a NON-REPRO you don't need to discover.
Step 0.76 — Latency-class bug? Cross-campaign cache-hit comparison (added 2026-07-21, #8501)
If the user's symptom is perceived latency ("still streaming for scene N", "next action takes 30+ seconds", "loading hangs"), the FIRST diagnostic move is NOT to grep code or check story-history size — it's to run a cross-campaign cache-hit comparison. The user's intuition "my OTHER campaign with more content was fine" is a strong signal; treat it as ground truth and verify.
-- Per-campaign cache-hit aggregation, last 30 days, gemini_provider.stream only
SELECT
campaign_id,
ROUND(SAFE_DIVIDE(AVG(IFNULL(cached_tokens, 0)), AVG(estimated_input_tokens)) * 100, 1) AS cache_hit_pct,
ROUND(AVG(prompt_tokens), 0) AS avg_prompt,
ROUND(AVG(story_tokens_est), 0) AS avg_story,
COUNT(*) AS n
FROM `worldarchitecture-ai.llm_forensics.llm_payloads`
WHERE ingested_at > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND agent = 'gemini_provider.stream'
AND estimated_input_tokens IS NOT NULL
GROUP BY campaign_id
HAVING COUNT(*) >= 5
ORDER BY cache_hit_pct ASC
LIMIT 25
If the slow campaign has cache_hit_pct < 60% AND a same-class baseline campaign has cache_hit_pct > 70% despite similar prompt size, the diagnosis is prompt-cache-invalidation churn (Gemini implicit-context-cache busting per turn), NOT story bloat. The full recipe — including the per-turn alternation pattern, the 3 cache-buster suspects (god-mode directive replay, long-form narrative module import, story-history truncation race), and the confirmatory BQ diff — lives in references/bq-llm-payload-truncation-pitfall.md §"Cross-campaign cache-hit comparison".
Why this step comes BEFORE the 3 static-evidence greps: if you start by grepping code, you'll go down the wrong path (likely "prompt bloat" or "story_history unbounded"). The cache-hit comparison nails the diagnosis in one query and points you at the right candidate code path (_build_story_history_bundle, _inject_god_mode_directive_text, _compose_system_prompt).
Latency-metric source of truth. Use worldarchitecture-ai.llm_forensics.latency_metrics.duration_ms (NOT llm_payloads.latency_ms — that column doesn't exist). The user's "still streaming" wait is flask_handler_first_narrative_chunk rows specifically — the Flask→SSE serialization latency, not the LLM inference latency.
Sibling-campaign structural-issue flag
When gh issue list shows ≥3 open repro issues against the same campaign_id (e.g. #8275, #8277, #8293 all on xK3fp5XrV24oarIINTF7), the campaign itself has a structural issue — likely the same root cause (render-and-persist, god-mode-directive-missing, npc-status-persistence) surfacing in different scenes. In the new issue body AND the matching bead body, explicitly link the prior siblings and note "3rd instance on this campaign — likely same root cause class." This prevents the issue from being triaged as a one-off and pushes the harness fix toward the shared underlying cause.
When the 3rd sibling fires: STOP filing per-scene issues. Branch a fresh worktree for a root-cause-first prompt fix that addresses the common anchor layer (the durable-fix PR still ships — see references/prompt-fix-deliverable-shape-2026-07-18.md for the 4-component deliverable shape; verified on jleechanorg/worldarchitect.ai PR #8446, 2026-07-18). Bead body for the cluster-trigger fix should reference the 3 sibling beads as dependencies (parent-child type) so br ready --parent ... surfaces the cluster root.
Branch + worktree when the durable-fix PR ships (added 2026-07-18, #8444)
NOTE (4.0.0): /repro no longer precreates a draft PR. This section applies only when the 3rd-sibling cluster trigger fires AND a root-cause-first prompt fix is being shipped (references/prompt-fix-deliverable-shape-2026-07-18.md 4-component deliverable). For one-off / per-scene repros, the issue + bead are sufficient; no branch is needed at the gate stage.
When creating the branch for the durable-fix PR, git checkout main may fail if main is already checked out at another worktree (error: fatal: 'main' is already checked out at '/path/to/worktree').
Fix: Use git worktree add <path> -b <branch> HEAD to create a new worktree on a fresh branch. This avoids disturbing the existing main checkout and is the standard pattern for repro branches:
cd ${HOME}/projects/worldarchitect.ai
git worktree add ${HOME}/projects/worktree_<slug> -b fix/<descriptive-branch>-<issue-number> HEAD
# Work in the worktree for PR commits
# Clean up with: `git worktree remove ${HOME}/projects/worktree_<slug>`
Worktree-silent-edit trap (added 2026-07-18, PR #8446)
The worktree created above has no ./venv/ (Python tests need the main checkout's venv). When you run patch or cp from the wrong CWD — typically because you cd'd to the main checkout to run pytest against the worktree's test files — the edits land in the main checkout silently. There is NO error message; the patch succeeds, pytest runs, tests pass. The trap: the edits look correct in the test run, but they were applied on a different branch than the PR branch.
Detection heuristic (mandatory after every patch on a worktree):
# After EVERY patch, run BOTH status commands:
cd ${HOME}/projects/wt-<topic>
git status --short # MUST show the patched files
git rev-parse --abbrev-ref HEAD # confirm branch is what you think
cd ${HOME}/projects/<main-checkout>
git status --short # MUST be empty after a worktree-local edit
If the main checkout shows the file as modified, the patch landed in the wrong place. The fix is to cp the file to the worktree and git checkout the file in the main checkout (NOT in the worktree — you want the worktree to keep the patch).
Verified case 2026-07-18, jleechanorg/worldarchitect.ai PR #8446: the prompt edits + test file landed in the main checkout first (no error). Caught via git status --short on both directories. ~3 tool calls wasted. Without the dual-status check, the PR would have shipped on the wrong branch (the main checkout's branch docs/bq-cost-spike-2026-07-08-findings, NOT the worktree's fix/aegon-rejoin-co-presence-8444).
Cross-reference: references/prompt-fix-deliverable-shape-2026-07-18.md §"Pitfall 1 — Edits landed in the wrong checkout (silent failure)" for the full recipe.
Case-fold + space-tolerant substring matching for contract tests (added 2026-07-20, #8490)
When pinning a contract that the prompt text contains a quantified threshold
(e.g. CR ≥ player_level / 2), write the substring assertion to match the
logical presence, not the literal characters. Two traps burned time
in the #8490 test loop:
-
Case-fold first, then assert. Unicode threshold characters (≥, ≤, ×, →) often survive case-fold but their ASCII fallbacks (
>=,<=,->) may not.assert "/2" in sectionfails on the lowercased stringplayer_level / 2because the spaces around/mean"/2"isn't a contiguous substring. Use the multi-word form:assert "player_level / 2" in section. -
Don't assert on bare punctuation. A test like
assert "≥ 3 combatants" in sectionpasses today, but if the prompt is edited toat least 3 combatants(semantic equivalent), the test breaks for the wrong reason. Assert on the semantic quantity ("3 combatants") AND the structural signature ("challenging"), not on the connector punctuation.
Pattern (verified on PR #8491, mvp_site/tests/test_combat_scope_classifier_8490.py):
section = _extract_classifier_section(_read()).lower()
assert "player_level / 2" in section # threshold present (multi-word)
assert "≥ 3 combatants" in section or \
">= 3 combatants" in section # combatant count (Unicode OR ASCII)
Why this matters: a brittle test that fails on the wrong reason sends you into the wrong fix path. If the test reports "threshold missing" but the threshold IS present in a slightly-different form, you waste a turn reading the prompt file again.
Contract-test resolver pitfall (added 2026-07-21, PR #8500)
A contract test that reads prompt files MUST resolve the repo root from the test file's own location, NOT from a hard-coded absolute path. The hard-coded path traps the test into reading the main checkout's prompt files, even when the test is running from inside a worktree that has the patched files.
Symptom: 6/6 tests fail with section-extraction errors (e.g. AssertionError: Section not found in planning_protocol.md), not assertion errors. The
extractor reads the prompt file, finds the file exists, but the substring
match returns None because the file it's reading is the un-patched main
checkout copy. There is no warning — pytest runs the test, the test runs the
extractor, the extractor opens the wrong file, and you only notice because
all tests are failing identically.
Detection: when ALL tests in a contract fail with extraction errors
(non-assert style errors at the _extract_* line), the resolver is
pointing at the wrong repo.
Fix (verified pattern, copy this into any contract test that reads
mvp_site/prompts/*.md):
import os as _os
def _resolve_repo_root():
env = _os.environ.get("HERMES_REPO_ROOT")
if env and _os.path.isfile(_os.path.join(env, "mvp_site/prompts/planning_protocol.md")):
return env
cur = _os.path.dirname(_os.path.abspath(__file__))
for _ in range(6):
cur = _os.path.dirname(cur)
if _os.path.isfile(_os.path.join(cur, "mvp_site/prompts/planning_protocol.md")):
return cur
return "${HOME}/projects/worldarchitect.ai" # last-resort fallback
REPO_ROOT = _resolve_repo_root()
Companion rule (verified same case): when a contract test lives in a
worktree (not the main checkout), pytest invoked from /Users/jleechan or
from ${HOME}/projects/worldarchitect.ai must still find the
worktree's prompt files. The env-var override (HERMES_REPO_ROOT=...) is
the reliable path. The walking-up resolver handles the default case
(running pytest from inside the worktree).
Verified worked example: mvp_site/tests/test_planning_block_npc_peer_autonomy_anchor_8499.py on PR #8500. Initial run with hard-coded
REPO_ROOT = "${HOME}/projects/worldarchitect.ai" had 6/6 failures
with extraction errors. Switched to the walking-up resolver; 6/6 green.
Cross-reference: references/prompt-fix-deliverable-shape-2026-07-18.md
§"Pitfall 1 — Edits landed in the wrong checkout" covers the complementary
trap (edits land in the main checkout because the agent cd'd to the main
checkout to run pytest). The contract-test resolver pitfall is the same
trap from the test side: the test reads the wrong file because the agent
hard-coded the main checkout path.
Failure handling
- GH auth fails → run the 4-token diagnostic in
references/gh-auth-resolution.mdBEFORE trustinggh auth status.gh auth statusis misleading (it can report "invalid" on a rate-limited token, and the old "SLACK_MCP_XOXB_TOKEN may be expired" text in this section was wrong on two counts: SLACK_MCP_XOXB_TOKEN is the Slack MCP token, and the actual fix is rarely "the token expired" — it's "the wrong token source" or "the token lacks org scope" or "the token is rate-limited"). Real failure modes: 401 (token revoked), 404 (token valid but lacks org scope for the private repo), 403 (rate-limited, not auth). See the reference for the full decision tree + worked example. gh issue createhits GraphQLrate limit exceeded(verified 2026-07-14 on #8390) →ghCLI uses GraphQL for issue create; when the per-user GraphQL budg
Truncated - read the full file at https://github.com/jleechanorg/jleechanbrain/blob/c33d1788ed3130e20ea624a05143b3fc6db9eed6/skills/repro/SKILL.md.