GitHub Error: 422_validation_failed — REST API Validation Failed
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"message": "Validation Failed",
"errors": [
{
"resource": "PullRequest",
"code": "custom",
"message": "A pull request already exists for owner:branch."
}
],
"documentation_url": "https://docs.github.com/rest/pulls/pulls#create-a-pull-request",
"status": "422"
}
GitHub’s 422 means “I understood your request but the data is invalid” — a semantic problem rather than a syntactic one. The response always includes an errors array; treating it as the source of truth (rather than the generic top-level message) turns 422s from baffling into mechanical.
The most common 422 in practice is uniqueness — already_exists for labels, deploy keys, webhook URLs, and one-PR-per-branch-pair. Treating these errors as success in idempotent automation eliminates the majority of 422 noise without changing your logic at all. The remaining cases are missing required fields, which schema validation in your client catches before the round trip.
Why this happens
- Missing required field. POST endpoints often require fields the docs only mention in passing. Creating an issue without `title`, opening a PR without `head`/`base`, or pushing a release without `tag_name` returns `code: missing_field` with the field name.
- Uniqueness conflict. GitHub enforces uniqueness on PR head/base pairs (one open PR per branch combo), label names per repo, deploy keys per repo, and webhook URLs. Re-submitting with the same key returns `code: already_exists` or `code: custom`.
- Referenced resource doesn't exist. Adding a non-existent label to an issue, requesting review from a user not in the org, or creating a PR with a `head` branch that no longer exists returns `code: invalid` on that field. The branch / label / user must exist before you can reference it.
- Bad SHA or ref format. Many endpoints accept a SHA or ref. A truncated SHA (less than 4 chars), a non-existent SHA, or a ref starting with `refs/heads/refs/heads/` returns 422 with `code: invalid` on the ref/sha field.
- Field value out of allowed range. Numeric fields (e.g., `per_page` max 100), enum fields (`state` must be `open` or `closed`), and string-length fields each have limits. Exceeding them returns 422 with `code: invalid` and the violating field name.
How to fix it
Fixes are ordered by likelihood. Start with the first one that matches your context.
1. Parse the errors array — don't just log message
The top-level `message` is always "Validation Failed" — useless for debugging. The actionable detail is in `errors[].code` plus `errors[].field`. Surface that to your logs and your users.
async function createIssue(owner, repo, body) {
const r = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (r.status === 422) {
const err = await r.json();
const detail = err.errors.map(e => `${e.resource}.${e.field || ''}: ${e.code} (${e.message || ''})`).join('; ');
throw new Error(`GitHub 422: ${detail}`);
}
return r.json();
}
2. Pre-validate against the schema before sending
For frequent calls (issue creation, PR creation, release publishing), validate locally against the OpenAPI schema. Catches missing fields and bad enums before the round trip.
REQUIRED_FOR_PR = ('title', 'head', 'base')
def validate_pr_body(body):
missing = [k for k in REQUIRED_FOR_PR if not body.get(k)]
if missing:
raise ValueError(f"Missing required fields: {missing}")
if body.get('head', '').startswith('refs/'):
raise ValueError("`head` should be a branch name, not a ref")
3. Handle already_exists idempotently
For "create" endpoints with uniqueness constraints, treat 422 with `already_exists` (or "already exists" in the message) as success. List the existing resource and continue rather than failing the whole job.
async function ensureLabel(octokit, owner, repo, name, color) {
try {
return await octokit.issues.createLabel({ owner, repo, name, color });
} catch (err) {
if (err.status === 422 && /already_exists|already exists/i.test(JSON.stringify(err.response.data))) {
return octokit.issues.getLabel({ owner, repo, name });
}
throw err;
}
}
4. Refresh stale references before retrying
A 422 with `code: invalid` on a SHA or branch usually means the ref moved. Fetch the latest `heads/<branch>` SHA and resubmit with the fresh value. Don't retry blindly with the same stale SHA.
5. Use the API explorer to confirm payload shape
docs.github.com/rest has a "Try it out" panel that constructs a known-valid payload for every endpoint. When in doubt, copy that payload, run it, then diff against what your code is sending.
Detection and monitoring in production
Tag 422 errors by `errors[0].resource` + `errors[0].code` so they cluster in your error tracker. Most 422s are bugs in your client code (you should never see them in steady state). A spike usually means a deployed schema change in your code or a deprecation in GitHub's API.
Related errors
- github403_rate_limitYou exceeded GitHub's primary REST API rate limit — 60 requests/hour for unauthenticated calls, 5,000/hour for personal access tokens, or 15,000/hour for GitHub App installations. The response is HTTP 403 with `X-RateLimit-Remaining: 0`.
- awsAccessDeniedExceptionThe IAM principal (user, role, or assumed role) making the request does not have an `Allow` statement for the action and resource being called, or an explicit `Deny` somewhere in the evaluation chain blocks it.
- stripecard_declinedThe customer's card issuer refused to authorise the charge. Stripe relays the issuer's decline reason in `decline_code` (e.g., `insufficient_funds`, `lost_card`, `do_not_honor`).
Frequently asked questions
Why does GitHub return 422 instead of 400? +
`code: custom` doesn't tell me what's wrong. How do I debug it? +
Should I retry a 422 automatically? +
Why does creating a PR with the same head/base twice return 422? +
I get 422 on a webhook create — but the webhook URL is valid. Why? +
Does 422 count against my rate limit? +
Why is my issue title rejected with 422? +
Can 422 ever come from a GET endpoint? +
When to escalate to GitHub support
Escalate to GitHub support if (a) the `errors` array is empty but the response is 422 — that's a server bug, (b) the validation message references a field your payload doesn't include, or (c) a request that worked yesterday returns 422 today with no schema change announcement on the GitHub Changelog. Otherwise, 422s are nearly always client-side fixes.