Summary
In github storage mode, saving on a branch protected by a GitHub organization / repository ruleset (with "Require a pull request before merging") shows a raw error instead of the "Changes must be made via pull request… Create a new branch" prompt. The same protection configured as classic branch protection works correctly.
I believe this is because the save-error handling only recognises the classic BRANCH_PROTECTION_RULE_VIOLATION GraphQL error type, and rulesets surface the violation differently.
Environment
@keystatic/core 0.5.51
- Storage:
github
- Branch protected by an org-level ruleset requiring a pull request (not classic branch protection)
Steps to reproduce
- Protect a repo's default branch with an organization ruleset whose rules include "Require a pull request before merging".
- In the Keystatic admin (github mode), on that branch, create or edit an entry and click Save.
Expected: the existing needs-new-branch flow triggers — Keystatic offers to create a new branch (as it does under classic branch protection).
Actual: the save fails with the raw underlying error; the branch-creation prompt is never shown.
Where it comes from (as far as I can tell)
In packages/keystatic/src/app/updating.tsx, the save handler routes to needs-new-branch only for the classic error type:
// ~line 231
if (gqlError.type === 'BRANCH_PROTECTION_RULE_VIOLATION') {
setState({ kind: 'needs-new-branch', reason: '…pull request…' });
return false;
}
The one FORBIDDEN branch further down (~line 288) is scoped to a different case — a missing GitHub App install:
err.type === 'FORBIDDEN' &&
err.message === 'Resource not accessible by integration'
A ruleset PR-required violation isn't the classic type and doesn't match that message, so it appears to fall through to the generic throw result.error. (I haven't captured the exact GraphQL error object for the ruleset case — happy to if that's useful; from the GitHub side the message is typically "Changes must be made through a pull request.")
Rulesets are now GitHub's recommended way to configure protection, so this path is increasingly common. I searched open/closed issues and PRs and didn't find this already reported — apologies if I missed it.
Possible approaches
Two options, smallest first:
- Match the ruleset violation near the existing handler and reuse the same
needs-new-branch path. Minimal and mirrors the classic case; relies on the error shape, which is a little less precise.
- Detect proactively via GitHub's structured rules API —
GET /repos/{owner}/{repo}/rules/branches/{branch} returns typed rules; a pull_request rule means direct commits are blocked. More robust (no error-string matching) but a larger change.
For context on the use case: we run Keystatic as the editing surface for a content repo whose default branch is governed by an org ruleset — that's how the org enforces PR review (and signed commits) uniformly across all its repos, so switching that branch to classic protection just to satisfy this path isn't really an option. Non-technical editors land on the default branch, try to save, and hit the raw error with no way forward, where the classic-protection flow would have guided them onto a new branch.
If a fix would be welcome, I can open a PR with a changeset and a test.
Summary
In github storage mode, saving on a branch protected by a GitHub organization / repository ruleset (with "Require a pull request before merging") shows a raw error instead of the "Changes must be made via pull request… Create a new branch" prompt. The same protection configured as classic branch protection works correctly.
I believe this is because the save-error handling only recognises the classic
BRANCH_PROTECTION_RULE_VIOLATIONGraphQL error type, and rulesets surface the violation differently.Environment
@keystatic/core0.5.51githubSteps to reproduce
Expected: the existing
needs-new-branchflow triggers — Keystatic offers to create a new branch (as it does under classic branch protection).Actual: the save fails with the raw underlying error; the branch-creation prompt is never shown.
Where it comes from (as far as I can tell)
In
packages/keystatic/src/app/updating.tsx, the save handler routes toneeds-new-branchonly for the classic error type:The one
FORBIDDENbranch further down (~line 288) is scoped to a different case — a missing GitHub App install:A ruleset PR-required violation isn't the classic type and doesn't match that message, so it appears to fall through to the generic
throw result.error. (I haven't captured the exact GraphQL error object for the ruleset case — happy to if that's useful; from the GitHub side the message is typically "Changes must be made through a pull request.")Rulesets are now GitHub's recommended way to configure protection, so this path is increasingly common. I searched open/closed issues and PRs and didn't find this already reported — apologies if I missed it.
Possible approaches
Two options, smallest first:
needs-new-branchpath. Minimal and mirrors the classic case; relies on the error shape, which is a little less precise.GET /repos/{owner}/{repo}/rules/branches/{branch}returns typed rules; apull_requestrule means direct commits are blocked. More robust (no error-string matching) but a larger change.For context on the use case: we run Keystatic as the editing surface for a content repo whose default branch is governed by an org ruleset — that's how the org enforces PR review (and signed commits) uniformly across all its repos, so switching that branch to classic protection just to satisfy this path isn't really an option. Non-technical editors land on the default branch, try to save, and hit the raw error with no way forward, where the classic-protection flow would have guided them onto a new branch.
If a fix would be welcome, I can open a PR with a changeset and a test.