Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
["3.2.0 (2026-07-21) \"Non-repro verification recipe for LLM-forgot-NPC-was-dead reports\" — verified on $GITHUB_REPOSITORY issue","3.5.0 (2026-07-22) **Factor H — prompt-renders-but-unbounded added to god-mode-directive-missing-subclasses.md** (verified on PR","3.2.1 (2026-07-21) \"two keys same character different status\" pitfall — verified on","3.1.0 (2026-07-21) **Latency-class diagnostic — Step 0.76 cross-campaign cache-hit comparison (issue",{"3.0.1 (2026-07-21) **NEW pitfall":"contract-test resolver pointing to the wrong repo (verified PR"},"3.7.1 (2026-07-23) **Step 0.77 worked-end-to-end on the 4th-sibling cluster (issue","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 $GITHUB_REPOSITORY issue","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 $GITHUB_REPOSITORY issue","2.8.0 (2026-07-21) **Factor G — prompt-side default missing added to god-mode-directive-missing-subclasses.md** (verified on $GITHUB_REPOSITORY issue","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","3.0.0 (2026-07-21) **NPC Peer-Autonomy — 4th canonical-state-anchor sub-class added (issue",{"3.0.1 (2026-07-21) **NEW pitfall":"contract-test resolver pointing to the wrong repo (verified PR"},"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 $GITHUB_REPOSITORY issue","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 $GITHUB_REPOSITORY issue","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 $GITHUB_REPOSITORY PR","2.1.1 (2026-07-18) **New pitfall (verified","2.1.2 (2026-07-18) **New pitfall (verified",{"2.0.0 (2026-07-18) Extended `references/repro-planning-block-and-campaign-cluster-2026-07-18.md` with 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"},"1.7.0 (2026-07-18) Added **Gate 0 — pre-flight rate-limit check** before invoking `gh-safe-publish` (verified on","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",{"1.6.0 (2026-07-12) Added references/json-serialization-leak.md — NEW bug class":"TypeError on `set`/`_Sentinel` leaking through `get_campaign` response_data, causing intermittent HTTP 500s (verified from issue"},"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","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",{"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"},"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","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:
WORLDAI_DEV_MODE=true is mandatory — scripts raise ValueError without it.
Hard gates (MUST execute, in order, before anything else)
1. ~/.hermes/scripts/gh-safe-publish issue create — File the GitHub issue immediately 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. ~/.hermes/scripts/gh-safe-publish pr create --draft — Precreate the draft PR through the outbound secret gate, linked to the issue. See canonical skill Step 1 for the exact command, replacing a raw gh pr create invocation with the gated wrapper.
Both gates must complete before proceeding to Step 0.75 (bug phenotype capture) or any other step.
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/<branch> (verifiable via
git rev-parse origin/<branch>), not on whether gates 1 and 2 happened
in one session or two.
Sequence (verified worked example: issue #8468 + draft PR #8469, branch
fix/eroa-targaryen-identity-knew-me, HEAD 6dfdd5bf27):
Write the issue body to ~/.hermes/wa-repro-<issue-no>/issue-body.md
(NOT /tmp/... — execute_code is sandbox-scoped per call).
Gate the body: python3 ~/.hermes/lib/outbound_secret_gate.py check --file <path>.
REST-POST the issue: urllib.request against
https://api.github.com/repos/<OWNER>/<REPO>/issues (REST fallback
when GraphQL quota is exhausted; same gate applies).
In the worktree: write the prompt-fix changes (canonical-state anchor
section + mirror + parallel test), commit, push.
REST-POST the draft PR against /pulls (head branch must already exist
on origin — push BEFORE the PR create, otherwise REST returns 422).
Post the consolidated reply in Slack with both URLs.
Why this works: the gate contract requires the issue be filed BEFORE
any other work (so the bug has a permanent record). It does NOT require
that the draft PR wait for a fresh session. The single-turn flow gets
both deliverables onto origin in one pass; the user reviews the draft
PR at their convenience and merges when CI + CR are green.
Before invoking gh-safe-publish (which calls gh issue create / gh pr 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 calls gh issue create / gh pr create under the hood. Both use GraphQL for the underlying mutation. 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.
"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 BETWEENTIMESTAMP('<BEFORE_TS>') ANDTIMESTAMP('<AFTER_TS>')
ORDERBY ingested_at ASC
Three checks the diagnostic must answer (with verified worked example offsets):
Did the LLM receive the rule? Grep request_json for 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_text for 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)=9 for 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>" $PROJECT_ROOT/ --include="*.py" and …/$PROJECT_ROOT/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/your-project.com/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 $GITHUB_REPOSITORY --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 PR description — 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.
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 onlySELECT
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(), INTERVAL30DAY)
AND agent ='gemini_provider.stream'AND estimated_input_tokens ISNOT NULLGROUPBY campaign_id
HAVINGCOUNT(*) >=5ORDERBY 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 PR description, 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. See references/prompt-fix-deliverable-shape-2026-07-18.md for the 4-component deliverable shape (verified on $GITHUB_REPOSITORY PR #8446, 2026-07-18).
Git worktree pitfall
When creating a branch for the draft 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/your-project.com
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>`
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 thinkcd$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, $GITHUB_REPOSITORY 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.
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 section fails on the lowercased string player_level / 2
because 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 section
passes today, but if the prompt is edited to at 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, $PROJECT_ROOT/tests/test_combat_scope_classifier_8490.py):
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.
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
$PROJECT_ROOT/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, "$PROJECT_ROOT/prompts/planning_protocol.md")):
return env
cur = _os.path.dirname(_os.path.abspath(__file__))
for _ inrange(6):
cur = _os.path.dirname(cur)
if _os.path.isfile(_os.path.join(cur, "$PROJECT_ROOT/prompts/planning_protocol.md")):
return cur
return"$HOME/projects/your-project.com"# 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 $HOME or
from $HOME/projects/your-project.com 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: $PROJECT_ROOT/tests/test_planning_block_npc_peer_autonomy_anchor_8499.py on PR #8500. Initial run with hard-coded
REPO_ROOT = "$HOME/projects/your-project.com" 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.md BEFORE trusting gh auth status. gh auth status is 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 create / gh pr create hit GraphQL rate limit exceeded (verified 2026-07-14 on #8390) → gh CLI uses GraphQL for issue/PR create; when the per-user GraphQL budget is exhausted, BOTH commands fail identically with GraphQL: API rate limit already exceeded for user ID 13840161.Do NOT retry — the budget won't refill for an hour. Fall back to urllib.request against https://api.github.com/repos/<OWNER>/<REPO>/{issues,pulls} with the same gh auth token. REST has a separate quota and gh api /rate_limit reports it as fresh. Full recipe (verified 2026-07-18 on #8451/#8452) is now in references/gh-rate-limit-rest-fallback.md — covers the urllib.request POST pattern for issues/PRs, the PATCH pattern for body updates, the head-ref-doesn't-exist pitfall, and the verification step.
Issue creation fails for non-auth reasons → stop entirely, do not proceed
Draft PR creation fails → stop (issue exists as record)
git checkout main blocked by existing worktree → use git worktree add instead
Never skip gates to "save time" — gates 1+2 exist so the bug has a permanent record. Posting the issue body to chat instead is NOT a substitute.
bq fails with ImportError: cannot import name 'bq_error' from 'utils' → PYTHONPATH is polluted (often by ~/projects_other/hermes-agent). Clear it: cd / && unset PYTHONPATH && bq query .... See references/god-mode-directive-missing-subclasses.md §"BQ forensic recipe" for the full pre-flight.
/repro invoked on a symptom that is NOT a campaign-state bug → pivot out of /repro and into the class-specific skill. The user may type /repro loosely to mean "investigate this deployed bug" — that is the intent, not the workflow. The /repro workflow is the campaign-state bug workflow (live URL → copy_campaign → reproduce → verdict). Off-ramps:
"the cookie expires too soon / I have to log in again" → wa-cloud-run-deploy-failure-debug (sibling class: references/auth-cookie-ttl-class-2026-07-24.md). The cookie Expires is set by Firebase Hosting's auth helper and re-emitted verbatim by $PROJECT_ROOT/main.py:1684-1696 with no server-side max_age override. No copy_campaign.py, no Firestore state read, no BQ llm_payloads query — the diagnostic is client_diag GCP logs + the proxy code path.
"the deploy failed / the revision isn't ready / the service is broken" → wa-cloud-run-deploy-failure-debug directly. Build log ≠ revision log; pull cloud_run_revision resource logs.
"the app is slow / still streaming" → Step 0.76 cross-campaign cache-hit comparison, but if the slowdown is on the deployed URL (not in a turn) it's an infra symptom, not a campaign-state bug.
"the LLM forgot my directive" → Step 0.77 BQ-first diagnostic (this is the only/repro sub-class that starts with a /repro invocation; the campaign is the medium, the diagnosis is the recipe).
"a key in campaign A did X wrong" → this IS /repro territory. Stay the course.
The user-facing pattern: post a 1-sentence acknowledgment that /repro is the wrong workflow, name the right one, then run it. Do NOT file a GitHub issue for a non-campaign-state bug — the hard gates 1+2 exist to record campaign-state repros. Verified: 2026-07-24, the user invoked /repro for the auth-cookie-TTL symptom on mvp-site-app-dev; the right answer was "this is operational, not campaign-state" + the 3-bullet diagnosis + a PR offer, NOT a gh issue create against $GITHUB_REPOSITORY.
Reference
references/repro-planning-block-and-campaign-cluster-2026-07-18.md — canonical planning-block emission reference (2026-07-18, extended #8444). Contains: (a) the 5-anchor taxonomy for planning_block canonical-state violations; (b) the NPC co-presence sub-class (anchor b) — user-reported symptom "planning blocks dont have the full information about the story while the narrative seems fine"; (c) copy_campaign.py positional-args + --allow-same-user flag gotchas; (d) Firestore REST pagination recipe (drop orderBy, paginate nextPageToken, sort client-side); (e) recommended durable fix shape for a single "Choice Premise Validation" section in $PROJECT_ROOT/prompts/planning_protocol.md covering all 5 anchors. The umbrella-level authority on the planning_block bug class — load this first when the symptom is "the planning block says X" OR when filing the 2nd+ repro on the same campaign_id.
references/prompt-fix-deliverable-shape-2026-07-18.md — NEW (2026-07-18, PR #8446) — the durable-fix recipe for planning_block canonical-state-anchor violations. Contains: (a) the 4-component deliverable shape (prompt section + narrative mirror + test file + PR body); (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, 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.
references/scene-466-dupe-narrative-inertia.md — NEW (2026-07-14, #8397/#8398): campaign-title-as-bug-name signal; cross-campaign cluster trigger extension (≥3 sibling repros sharing a title keyword across DIFFERENT campaign_ids, not just same campaign_id); structural narrative-frame dupe sub-class (gemini response re-uses previous scene's timestamp + character motif + frame when user introduces a new directive); HISTORICAL RED ARTIFACT threshold definition (quantitative N/M vs qualitative N=1 structural); story-doc schema pitfall (text/part/mode/timestamp/actor only — derive scene number from timestamp-sorted index, doc IDs are random); Gate 0 rate-limit pre-flight (proactive gh api rate_limit check before any gh issue create/gh pr create)
references/god-mode-grant-mechanic-not-operationalized.md — NEW sibling class (2026-07-10): custom features granted in narrative but never written as structured schema (trigger_check_types / formula / dice_expression). Distinct from god-mode-directive-missing (which is about text directives being dropped on the way to LLM payload). Verified from campaign xK3fp5XrV24oarIINTF7 issue #8320 / PR #8321.
references/god-mode-directive-enforcement.md — god-mode directives are advisory-only (no runtime enforcement); _should_reject_directive filter patterns; investigation pattern for god-mode bugs. Expanded/superseded by references/god-mode-directive-missing-subclasses.md for ANY multi-instance repro — load that one first when the issue is "LLM ignored my directive" and the canonical advisory-only finding may not be the active root cause.
references/god-mode-directive-missing-subclasses.md — the 4-factor matrix (A: streaming save-drop, B: wrong-storage routing, C: stale streaming bundle, D: backend override) with the BQ forensic recipe (## active god mode directives header check) and history of the 7 sibling instances across #7162/#8012/#8080/#8103/#8065/#8275/#8283 (as of 2026-07-08). Use whenever a repro is the 2nd-or-later instance of the recurring god-mode-directive-missing bug class.
references/god-mode-directive-factor-g-prompt-default-missing.md — NEW (2026-07-21, #8497): Factor G — the 7th sibling of the directive-pairing-invariant family. Distinct from F: LLM correctly writes directives.add AND the directive propagates to subsequent turns, but the prompt layer has no compact default-classifier rule stating which side of a multi-aspect mechanic is canonical. User is forced to god-mode-reinforce on every fresh campaign that imports the mechanic. Verified on campaign q04GfOEl4SWnEQrFUVST turn 31 (Mantle of the Radiant Slayer — Sanguine Sovereign vs Chitinous Ruin). Fix shape: 3-file deliverable (default-classifier § in the relevant prompt + worked example in god_mode_instruction.md + 6-test contract). Load when the user reports "LLM keeps picking the wrong aspect/variant/stance" on a multi-aspect mechanic — and the LLM IS writing the directive (verify with BQ directives.add count in the god-mode turn).
references/evidence-extraction-patterns.md — how to extract directive violations from exported campaign data (game state JSON + story text search, scene mapping, correction detection)
references/npc-status-persistence-bug.md — narrative-only state change bug class (LLM emits "X captured" in narrative but never writes state_updates.npc_data[X].status). Expanded 2026-07-08 to cover FOUR sub-classes: missing-write, wrong-write, prompt-anchor hallucination, AND confused-state with/replace (added 2026-07-08 from issue #8283). Three worked examples: scenes 50/73 (Aemond capture), scenes 86/89/97/100/134/149/151 (Aemond capture + "we share a father" lineage), and scene 315 (Visenya's parentage confused-state). Includes the scene-by-scene state_updates.npc_data[<NPC>] extraction table pattern — the canonical diagnostic for this class.
references/firestore-path-and-uid-resolution.md — concrete mechanics the canonical skill leaves under-specified: Firestore doc-path map (users/{uid}/campaigns/{cid}/game_states/current_state is THE canonical game state), email→UID resolution via campaign_manager.py find-user (NOT Firestore field lookup), story-doc schema, fresh-worktree venv pitfall (worktree has no ./venv/, use main checkout's absolute path), env-var discipline, first-touch pre-state capture rule. Project ID for the Firestore client is worldarchitecture-ai (with -ture-), NOT worldarchitect-ai — confirmed 2026-07-08 from cat $HOME/serviceAccountKey.json | jq .project_id.
references/auth-gate-fallback-repro.md — when the deployed URL is gated behind Firebase / Google sign-in and you don't have the user's browser session. Step 7 (NEW 2026-07-19): direct JS-state injection for fix-verification-only repros when Firebase PASSWORD_LOGIN_DISABLED blocks headless auth and the fix code path doesn't actually need auth. Covers the 7-step recipe, pitfalls (TESTING_AUTH_BYPASS+ALLOW_TEST_AUTH_BYPASS both required, header name, PYTHONPATH for infrastructure/ import, X-Test-Bypass-Auth is server-side only, bundle-level apiKey placeholder, port collision with MCP servers, copy_campaign.py --format json early-exit), and verified worked examples (issue #8250 / PR #8251; issue #8459 / draft PR #8460 for Step 7).
references/gh-auth-resolution.md — diagnostic decision tree for "gh auth / gh api fails but git works" — the canonical /repro hard-gate blocker. Covers token-source disambiguation (hosts.yml vs osxkeychain vs env vs AO_BOT_GH_TOKEN), rate-limit vs 401 vs 404 distinction, the GITHUB_TOKEN disabled-by-design pitfall (~/.bashrc:721-726, 1284-1285), a 4-token probe matrix, and a verified worked example (2026-07-08, the $GITHUB_REPOSITORY 404 across 4 tokens).
references/find-new-campaign-id-after-copy.md — recipe for finding the new campaign ID after copy_campaign.py returns only {dest_uid, dest_email} JSON. Uses Firestore REST API directly (not --find-by-title which times out on 2900+ campaign accounts). Verified worked example from #8283, 2026-07-08.
references/static-evidence-sufficient-no-live-turn.md — when the §2.1 first-touch rule is satisfiable entirely from pre-state + bug-origin story doc, without a live LLM turn. Lists the 3 static signals that satisfy the rule, the cases where a live turn IS required, the HISTORICAL RED ARTIFACT label for the verdict table, and a cost/benefit note. Verified from #8283, 2026-07-08 (5x faster time-to-verdict).
references/two-pronged-render-and-persist-bug.md — the "one symptom, two independent root causes" debugging pattern. When a user reports "LLM ignores my correction" + "field appears wrong", it's usually a render-side bug (field exists in Firestore but prompt constructor can't read it) AND a persist-side bug (corrections added to directives.add are silently dropped). Covers diagnostic recipe, proximate-cause-vs-underlying-cause distinction, sibling instances. Verified from issue #8283, 2026-07-08 (issue was actually 2 separate PRs, not 1).
references/phenotype-lock-static-evidence.md — the 3 static-evidence greps (code-symbol grep in $PROJECT_ROOT/ + $PROJECT_ROOT/prompts/, prior-export grep in /tmp/your-project.com/repro-exports/, sibling-issue scan via gh issue list) to run BEFORE asking the user the phenotype-anchor questions in Step 0.75. When the user-reported bug name doesn't match any code symbol, the bug is natural-language LLM prose, not a canonical game-state field — this narrows the bug class without spending an LLM call. Verified from #8293, 2026-07-09.
references/json-serialization-leak.md — NEW sibling class (2026-07-12): TypeError: Object of type {Sentinel,set} is not JSON serializable at $PROJECT_ROOT/main.pyget_campaign → return jsonify(response_data). Intermittent 500s from /api/campaigns/; second click within ~10s succeeds. Affects 3+ confirmed campaigns under one user (a1OGXHNxNdw1Id0iRfpR, 5MYrGMUZovrK6hgv3Qiu, FsiyESY987DF2lfgolCI). Confirms the "some campaigns load, others don't" user pattern is real, not auth. Verified from #8353.
references/npc-status-persistence-bug.md — narrative-only state change bug class (LLM emits "X captured" in narrative but never writes state_updates.npc_data[X].status). Expanded 2026-07-14 to SIX sub-classes: missing-write, wrong-write, prompt-anchor hallucination, confused-state with/replace (added 2026-07-08 from issue #8283), dual-entry canonical-state conflict + narrative-inertia spatial hallucination (added 2026-07-14 from issue #8390). Worked examples: scenes 50/73 (Aemond capture), scenes 86/89/97/100/134/149/151 (Aemond capture + "we share a father" lineage), scene 315 (Visenya's parentage confused-state), and story doc gg8CSnuTxGds4CxcqY0b (Jacaerys at Highgarden, campaign RMCPAPdfuErh8MgRuj6n). Includes the scene-by-scene state_updates.npc_data[<NPC>] extraction table pattern — the canonical diagnostic for this class.
references/state-update-value-derivation-drift.md — NEW (2026-07-23, #8528): the 7th sub-class of npc-status-persistence-bug. LLM correctly derives the value in narrative prose, correctly writes the structured field, but writes the wrong numeric value (typically ~50% under/over the canonical derivation). Signature distinction: narrative derivation correct AND emitted + field path correct + field type correct + field written — only the numeric drifts between narrative-computation and structured-transcription. Verified on wc2BBcSgOljiU3vJ160A scene 454 (Ao L95 → equipment_bonus=2 should be floor(95/10)=9). 4-component durable fix shape: state-update value-derivation block + select_directives_by_budget() + 12-test contract + CI lint scripts/check_state_update_value_drift.py. Load when Step 0.77 surfaces a bug where narrative-text formula-application is correct but state_updates.* field value is wrong.
references/gh-rate-limit-rest-fallback.md — NEW (2026-07-14, #8390): when gh issue create / gh pr create hit GitHub GraphQL rate limit mid-session, fall back to urllib.request REST API with the same gh auth token. Verified worked example: #8390 + #8391 created via REST when gh GraphQL was exhausted. Hard gates 1+2 MUST complete — REST fallback preserves the gate contract.
references/repro-llm-invented-lore-artifacts-2026-07-18.md — NEW (2026-07-18, #8443): 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 campaign D3iZvnGiBl9wyveQBFj9. Covers: bug-class signature (5 invention patterns — tracking foci, magic-tier violations, "-tuned" modifiers, visual shorthand glowing props); bypass-Flask-SSE replay technique (agy --print for >100K-token prompts exceeding the 320s Flask SSE timeout); three-replay red/green/control proof pattern; durable fix shape for narrative_system_instruction.md NPC Development section. Verified worked example: Blood-Scent silver vial + violet light + Vaelaros-tuned focus for a LOW-MAGIC Reach NPC. Pairs with the PR #8442 sibling fix for MBTI/ISTJ letters in player-facing prose — both bugs share the same root cause class (LLM invention at emit side) and the new canon-anchoring prompt rule covers both.
references/god-mode-directive-missing-subclasses.md — the 7-factor matrix (A: streaming save-drop, B: wrong-storage routing, C: stale streaming bundle, D: backend override, E: god-mode audit used to DEFEND a narrative bug — added 2026-07-14 from #8390, F: narrative-ack-as-write — added 2026-07-20 from #8490, H: prompt-renders-but-unbounded — added 2026-07-22 from PR #8477 review) with the BQ forensic recipe (## active god mode directives header check) and history of the 9 sibling instances across #7162/#8012/#8080/#8103/#8065/#8275/#8283/#8390/#8490. Use whenever a repro is the 2nd-or-later instance of the recurring god-mode-directive-missing bug class OR when a god-mode audit response feels defensive rather than corrective OR when the user reports "my directive was forgotten" without an obvious infrastructure bug (Factor F is a narrative-ack-as-write failure — the LLM satisfied the user's wording in prose but never emitted directives.add; Factor H is prompt-bloat at scale — the directive IS in the payload but the LLM can't attend to a 33K-char block, distinct from all others because persistence + delivery both work).
references/prompt-delivery-vs-content-2026-07-20.md — NEW (2026-07-20): the prompt-delivery vs prompt-content decision tree. When the LLM violates a rule and the rule is in the prompt file on disk, the bug class is delivery (file doesn't reach the LLM), not content (rule is wrong). This is a separate bug class from the god-mode-directive-missing family — that one is about text directives being dropped on the way to LLM payload; this one is about the WHOLE prompt file (Major Event Rarity Budget, Trigger Whitelist, etc.) being replaced by a 7-line stub at the dispatcher. Verified on PR #8005 (2026-06-29) where living_world_instruction.md was moved to dynamic_instructions for cache stability but only the activation tail was kept. Symptom signature: user reports a prompt-forbidden behavior; grepping the prompt file finds the rule; BQ request_json shows the file is NOT in the request — only a stub marker like del advances_time # Unused after <prompt> moved to dynamic path in agent_prompts.py:1469 confirms the seam. Cross-reference: use the new umbrella skill llm-prompt-delivery-audit first for the 5-step wire diagnostic — this reference records the worldarchitect-specific case study + how it differs from god-mode-directive-missing.
references/bq-llm-payload-truncation-pitfall.md — NEW (2026-07-21): the worldarchitecture-ai.llm_forensics.llm_payloads.request_json column is uniformly capped at ~350KB regardless of actual payload size. Filtering by REGEXP_CONTAINS(CAST(request_json AS STRING), '<CID>') returns 0 hits even when the campaign is there — always filter by the top-level campaign_id column instead. When BQ + Cloud Logging are both empty for a campaign, the user's session never reached the backend; the bug is client-side (auth state, pre-prompt-state modal, abort signals), not a payload limit. Diagnostic order: BQ top-level filter, then Cloud Logging filter, then JS code path read, then local Flask end-to-end repro. Includes 4 pitfalls (textPayload=null vs jsonPayload.message, gcloud logging read httpRequest.url INVALID_ARGUMENT, response_text truncation, PYTHONPATH pollution from ~/projects_other/hermes-agent). Added 2026-07-21: two more pitfalls — (1) turn vs turn_index column name (query WHERE turn = N returns 400 Unrecognized name: turn; use turn_index); (2) scene-number-vs-turn_index mismatch — the user-reported scene N often doesn't correspond to any turn_index in BQ (verified on #8497: user said "scene 77" but MAX(turn_index) = 32). Always confirm with SELECT MAX(turn_index) before assuming the user's scene ref.
references/god-mode-directive-factor-g-prompt-default-missing.md — NEW (2026-07-21, #8497): Factor G — the 7th sibling of the directive-pairing-invariant family. Distinct from F: LLM correctly writes directives.add AND the directive propagates, but the prompt layer has no compact default-classifier rule. Verified on Sanguine Architecture campaign q04GfOEl4SWnEQrFUVST turn 31 (Mantle of the Radiant Slayer). Fix shape: 3-file deliverable mirroring PR #8491.
references/repro-npc-peer-autonomy-anchor-2026-07-21.md — NEW (2026-07-21, #8499 / PR #8500): NPC Peer-Autonomy — the 4th canonical-state-anchor sub-class. Triggered by 3rd-sibling cluster on q04GfOEl4SWnEQrFUVST (alongside #8490, #8497). User symptom: planning block emits "Send ahead" / "Dispatch to ..." premises that presuppose dispatch authority over an independent_ally NPC. Root cause: planning_protocol.md## Canonical-State Anchor section had §4–§7 (Co-Presence / Directive Compliance / Reachability / Status Alignment) but was missing §8 (NPC Peer-Autonomy). Fix shape (verified): new ### 8. NPC Peer-Autonomy subsection in planning_protocol.md with 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 from the source campaign + Quick Self-Audit §7; mirror ### 9. NPC Peer-Autonomy in narrative_system_instruction.md forbidding the narrative-side "follows you" / "travels with you" / "at your side" framing; 6-test contract in $PROJECT_ROOT/tests/test_planning_block_npc_peer_autonomy_anchor_8499.py. Detection recipe: read npc_data.<name>.relationship first; if unset, fall back to scanning god_mode_directives[] for trigger phrases (Independent Allies / Peer Autonomy / Allied Peerage / own agenda / his own thing / doing his own thing / you cannot order them). Load when the user reports "the planning block treats as a minion I can dispatch" OR "planning block still acts like travels with me" on a campaign where the narrative has already established the NPC as an independent peer.
references/unbounded-scaling-l30-level-up-bug-class-2026-07-21.md — NEW (2026-07-21, #8508): 5th canonical-state-anchor sub-class — unbounded-scaling stale pending level-up. Symptom: "god mode stuck saying level N isn't complete" for L30+ characters (+50,000 XP/level regime). ensure_level_up_rewards_pending (game_state.py:2094-2128) has three stale-clear branches; none fires when current_level >= MAX_LEVEL=30 AND current_xp >= xp_threshold_for_pending AND target_level > current_level. God-mode admin-commit (agents.py:1142-1158) explicitly forbids writing rewards_pending — architectural asymmetry. Verified cross-campaign on q04GfOEl4SWnEQrFUVST (L50→L51) + wSm8Z8McTLJ8oQjqlTyJ (L77→L78). Fix shape: 3-component (backend gating game_state.py:2150+ + god-mode universal-override prompt rule agents.py:1151+ + parameterizable XP progression via DEFAULT_XP_PROGRESSION + custom_campaign_state.progression_overrides) + 15-test contract. Operator-directive embedding pattern: when the user issues 2-3 mid-/repro directives in the same flow ("all fields writable", "progression shouldn't be hardcoded"), ship ONE PR covering all directives as a coherent 3-component fix. Verified worked example: PR #8509 (branch fix/unbounded-scaling-stale-clear-8508 HEAD 778e705b84) — 4 files / +486/-12. Load when user reports "stuck saying level N" or "god mode can't clear pending level-up" on a character at L30+.
references/dropped-repro-parallel-evidence-2026-07-28.md -- NEW (2026-07-28): dropped-thread /repro recovery recipe for the specific shape where a fresh session opens with the user asking why the message was missed. Pairs with dropped-messages skill matching section. Run static-evidence in a single parallel first turn (export story.txt + game_state.json, grep verbatim user quote, read LLMs own acknowledgment scene, cross-reference prior-merged PRs, read agents.py / intent_classifier.py routing). Post verdict + ONE clarifying question if needed. Arm one-time 20-min follow-up cron in the same reply.
references/gemini-model-code-exec-allowlist-mismatch-2026-07-30.md -- NEW (2026-07-30): model capability allowlist-vs-API-behavior gap bug class. Captures the verified finding that a static allowlist read alone cannot detect infinite-loop models — gemini-3.5-flash-lite and gemini-3.6-flash are NOT in MODELS_WITH_CODE_EXECUTION AND infinite-loop on code_execution tool calls (finish_reason: TOO_MANY_TOOL_CALLS, 48 code parts, 21-27k tokens). Diagnostic recipe: live API probe with code_execution tool + temp=0.7, verify finish_reason + code_parts count. Also documents the working auth path: gcloud secrets versions access latest --secret=gemini-api-key --project=worldarchitecture-ai returns a live key (AIzaSyAvyb...) while ~/.gemini_api_key_secret is dead (API_KEY_INVALID). Load before adding ANY new model to a code-execution or tool-use allowlist in any your-project.com PR — the recipe generalizes to any "model claims tool support" verification.
references/non-repro-verification-recipe.md — NEW (2026-07-21, #8506): 5-step non-repro verification recipe for the user-perceived "LLM forgot NPC was dead / resurrected / ignored death" class of reports. Verified worked example: campaign q04GfOEl4SWnEQrFUVST, scene 189 — user reported "the LLM forgot Wyll and Jaheira were dead", but npc_data.Wyll.status="missing" + npc_data.Wyll Ravengard.status="dead" + npc_data.Jaheira.status="dead" confirmed canonical state was honored; LLM scene-189 prose explicitly said "Wyll and Jaheira are not here. They are the shadows at the edge of your golden dawn". Closed state_reason=not_planned. Recipe: (1) copy with --story-max-user-scene-number=N to capture the user's exact scene; (2) download state.json + story.txt; (3) grep npc_data[<NPC>].status for the user's claimed status (case-insensitive bare name AND surname-composed form); (4) grep story.txt for action-verb patterns attached to the NPC name AFTER the recorded death timestamp (0 hits = honored state, ≥1 hit = real bug); (5) write a 4-sentence diagnostic comment (one-line verdict + canonical status block + scene-by-scene evidence + suggested user follow-up) and close via REST PATCH state_reason=not_planned. Pitfalls included: resurrection-ritual false-positives (Speak with Dead canon-anchored, NOT a bug) + NPC-name fragmentation (Wyll vs Wyll Ravengard two-key split). Load when the user's report matches the pattern "LLM forgot X" / "X is acting alive but should be dead" / "X was resurrected without me asking". This recipe prevents the most common bug-class misclassification — canonical-state bugs are RARE (~10-20% of user reports in this category); user-misreads of honor-the-state prose are the rest.
references/god-mode-directive-writeback-gap.md — NEW (2026-07-23, #8528): the canonical-state asymmetry god-mode responses face — select_memories_by_budget() writebacks core_memories[] on every LLM turn but there is NO equivalent writeback for god_mode_directives[]. User-typed directives only survive as LLM-authored paraphrases in contents[] history; never lands in custom_campaign_state.god_mode_directives_snapshot as structured entries. Verified on wc2BBcSgOljiU3vJ160A scene 454 — 4 corrections across 6 minutes, 0 of the 7 directive entries emitted by the LLM landed in canonical state. Companion to references/god-mode-directive-routing-architecture.md (channel-side fix: WHERE directives travel) and references/state-update-value-derivation-drift.md (state-update fix: numeric values in npc_data). The triad (routing + writeback + state-update) must all ship together. 2-half fix shape: Half 1 = writeback_god_mode_directives() mirror of select_memories_by_budget writeback fires on every GodModeAgent turn; Half 2 = read snapshot into system_instruction with cache_control on next turn (so the canonical snapshot becomes the cache-stable authoritative source). Includes BQ detection recipe, verified before/after worked example, and the canonical-vs-operational directive distinction (operational = planner output, advisory; canonical = snapshot entry, authoritative). Load when user asks "why don't we have a record of [directive X] anywhere?" or when a 3rd-sibling repro shows paraphrase drift but no canonical-state record.