GitHub Issues API Returns 422 When Whisper Batch Body Contains Invented Repro Steps - How to Fix
Problem: Your playtest triage script calls POST /repos/{owner}/{repo}/issues (or gh issue create) after a Whisper batch night and GitHub responds 422 Unprocessable Entity. The automation log shows validation errors on body, labels, or issue form fields—while curl rate limits and 401 auth are not the cause. Batch triage stalls; facilitators paste the same LLM draft into the web UI and get the same rejection.
Who is affected now: Micro-studios running June–October 2026 Whisper→GitHub tag extraction with structured issue forms and nightly gh automation. GitHub validates issue bodies against YAML form schemas; LLM summaries that add “Repro steps (guess)”, hardware steps the player never said, or empty required fields fail before an issue is created. This is payload validation, not MKV concat failure (no transcript) or OpenAI 413 chunking (cloud Whisper size).
Fastest safe fix: Stop the batch job → open the 422 response JSON → remove invented repro → submit quote-only body with build_id from BUILD_RECEIPT → run gh issue create --dry-run (or REST preview) → set github_payload_ok: true on whisper_issue_tag_receipt_v1.json → retry API with minimal body + labels that exist on the repo.
Direct answer
422 means GitHub rejected the issue payload, not that Whisper failed. Playtest triage bots often paste LLM-expanded repro that violates your issue form (“repro steps known?” dropdown) or exceeds field rules. Fix the body, not the transcript: verbatim player quote, build_id, fragment_id, and “No — quote-only triage” unless the tester literally stated steps. Run gh issue create --dry-run before W6 marks github_issue_allowed: true.
Why this issue spikes in June–October 2026
- Issue forms adoption — Teams enable GitHub Issue forms to block free-text repro; API payloads must match form field IDs.
- Whisper batch nights — Local Whisper pipeline produces transcripts; facilitators want same-night GitHub issues.
- LLM assist creep — Summaries add “click Settings → Graphics” steps not in the transcript—W4 discipline exists in Guide #28 preflight but batch scripts skip the human gate.
- BUILD_RECEIPT gates — Lesson 266 fails promotion when
whisper_issue_tag_okis false after 422 storms. - November fest close-out — jq capstone crosswalk treats missing tag receipt as child receipt RED.
Symptoms and search phrases
- HTTP 422 on
POST .../issueswith"message": "Validation Failed". gh issue createexits 1; stderr mentions issue form or required field.- Body contains “Repro steps (automated)” or GPU/driver steps absent from transcript.
- Labels like
P0-blockerrejected—label does not exist on repo. build_idfield empty or copied from Discord instead of BUILD_RECEIPT.- Rate limit (403) confused with validation—422 has no Retry-After for payload fix.
- Web UI “Create issue” works with manual quote-only paste but API fails with bot body.
Root causes (check in order)
- Invented repro steps in
body—violates quote-only policy (W4) and often issue form dropdown rules. - Issue form required field empty (
build_id,player_quote,fragment_id). - Invalid labels array—typo or label not created on repo.
- Assignees/milestone IDs wrong for REST payload (less common than body).
- Body too large or contains characters breaking form JSON (rare vs invented repro).
- Template mismatch—API uses classic issue body while repo expects form-encoded field map.
- Human gate skipped (W5)—bot publishes before producer scrubs LLM draft.
Beginner path (first 30 minutes)
Prerequisites: gh CLI authenticated, transcript .txt for the clip, BUILD_RECEIPT.json with build_id, Whisper tag map preflight W1–W4 GREEN.
- Reproduce with
gh issue create --dry-runusing the failing title/body file. - Read 422 JSON—note which field failed (
player_quote,repro_verbatim, etc.). - Replace body with quote-only template (Step 3 below)—max one verbatim quote block.
- Confirm
build_idmatches BUILD_RECEIPT jq output (W2). - Retry dry-run → exit 0 → live create one issue → file receipt
github_payload_ok: true.
Common mistake: Retrying the same LLM body with exponential backoff—422 is not transient.
Fastest safe fix path
Step 1 — Capture the 422 response (developer)
REST:
curl -sS -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/ORG/REPO/issues" \
-d @/tmp/issue_payload.json | jq .
Note errors[] entries—often "field": "body" or form field IDs.
gh CLI:
gh issue create --title "Playtest P0 — fragment_03" \
--body-file /tmp/issue_body.md \
--label "P0-blocker" 2>&1 | tee /tmp/gh_422.log
Step 2 — Strip invented repro (W4 scrub)
| Keep | Remove |
|---|---|
| Verbatim quote from transcript | “Steps to reproduce (AI)” |
build_id from BUILD_RECEIPT |
Guessed GPU/driver steps |
fragment_id (mkv filename) |
Merged timeline from wrong clip |
| “No — quote-only triage” | “Probably click Options → …” |
Cross-check quote substring:
QUOTE="I can't leave the tavern after the quest"
grep -F "$QUOTE" release-evidence/playtest/transcripts/fragment_03.txt
Fail grep → do not open issue; ask facilitator for clearer clip.
Step 3 — Quote-only body template (minimal 422-safe)
## Playtest VOD (quote-only)
**build_id:** `fest-demo-2026-10-rc3` (from BUILD_RECEIPT)
**fragment_id:** `fragment_03.mkv`
**repro_known:** No — quote-only triage
> "I can't leave the tavern after the quest — the door doesn't work."
Transcript sha256: `abc123…` (W1 receipt field)
For issue form repos, map fields to form IDs in gh issue create or use the web UI once to export the expected JSON shape from GitHub docs.
Step 4 — Validate labels and dry-run
# Labels must exist
gh label list | grep -F "P0-blocker"
# Dry-run (GitHub CLI 2.40+)
gh issue create --dry-run \
--title "Playtest P0 — tavern soft lock (quote-only)" \
--body-file /tmp/issue_body_quote_only.md \
--label "P0-blocker"
Pass: exit 0, no 422 in output.
Step 5 — whisper_issue_tag_receipt_v1.json gate
{
"schema": "whisper_issue_tag_receipt_v1",
"build_id": "fest-demo-2026-10-rc3",
"fragment_id": "fragment_03.mkv",
"transcript_sha256": "abc123…",
"gates": {
"W4_repro_steps_invented_blocked": true,
"W5_human_gate_before_api": true,
"W6_github_payload_ok": true
},
"github_payload_ok": true,
"github_issue_allowed": true,
"issue_urls": [
"https://github.com/ORG/REPO/issues/1842"
]
}
Pin under release-evidence/playtest/whisper-tags/WHISPER_ISSUE_TAG_RECEIPT.json.
Fail-closed jq:
jq -e '.github_payload_ok == true and .github_issue_allowed == true' \
release-evidence/playtest/whisper-tags/WHISPER_ISSUE_TAG_RECEIPT.json
Pair Lesson 266 BUILD_RECEIPT promotion.
Verification
| Check | Pass when |
|---|---|
| Dry-run | gh issue create --dry-run exit 0 |
| Quote integrity | Issue body substring ⊆ transcript file |
| BUILD_RECEIPT W2 | build_id jq match |
| Live issue | HTTP 201; URL in receipt issue_urls[] |
| Receipt | github_payload_ok: true |
| BUILD_RECEIPT | whisper_issue_tag_ok: true after promotion |
Prevention
- Never pipe LLM output directly to GitHub API—draft tag map only (Guide W3–W5).
- Use issue forms with “No — quote-only triage” default for playtest template.
- CI:
gh issue create --dry-runon PRs that touch triage scripts. - Pre-create labels
P0-blocker,P1-major,playtest,fest_public. - Order receipts:
concat_ok→loudness_spot_ok→ Whisper → tag receipt (Audacity loudness evening). - Separate crash symbolicate issues from playtest quote issues (Q4 diligence blog).
Developer branches
Branch A — Invalid label 422
gh label create "P0-blocker" --color "B60205" --description "Playtest blocker"
Remove unknown labels from payload; retry dry-run.
Branch B — Issue form field IDs
Export template from .github/ISSUE_TEMPLATE/playtest-quote-only.yml. Map API body to form fields or use gh issue create with template flag if supported on your CLI version.
Branch C — Rate limit vs 422
| HTTP | Meaning | Action |
|---|---|---|
| 422 | Payload invalid | Fix body/labels |
403 + X-RateLimit-Remaining: 0 |
Rate limit | Backoff |
| 401 | Bad token | Refresh GITHUB_TOKEN |
Do not backoff on 422.
Comparison — three triage failures
| Symptom | Lane | Fix doc |
|---|---|---|
| No transcript file | Concat / path | MKV concat help |
| Whisper silent / clipped | Loudness | Audacity loudness preflight |
| 422 on issue create | Payload / invented repro | This help |
Related GamineAI reads
- Whisper playtest tag extraction strategy — W1–W6 + issue form YAML
- Whisper transcript tag map preflight (Guide #28) — ninety-second human gate
- Lesson 266 — Whisper issue tag BUILD_RECEIPT
- Lesson 207 — Whisper path decision tree
- Local Whisper playtest VOD pipeline
- Tuesday Discord CSV ingest ritual — async text cousin, not VOD quotes
- Official: GitHub REST Issues API
FAQ
Is 422 always invented repro?
No—check labels, assignees, and required form fields first. Invented repro is the most common cause in Whisper batch automation.
Can we disable issue forms for the bot?
Prefer quote-only forms over disabling—they prevent human facilitators from pasting bad repro too.
Does GitHub Copilot cause 422?
Copilot drafts in the web UI are human-reviewed; batch API failures come from unsupervised LLM bodies.
Same receipt as January batch?
No—january_whisper_batch_receipt_v1 owns batch order; whisper_issue_tag_receipt_v1 owns GitHub gate.
February live week same-day triage?
Pair February same-day Whisper strategy—422 fix applies before P0 issues hit pager stand-up.
Need Help when concat never completes?
Fix MKV concat first—no transcript means no quote to validate.
422 on playtest issue create is almost always a bad payload—scrub invented repro, dry-run gh issue create, file github_payload_ok on whisper_issue_tag_receipt_v1.json, then retry the API.