mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-25 23:11:31 +00:00
3cba608bb1
When GitHub returns 'GH013: Repository rule violations found' during
a push, the failure is currently silently swallowed by handleCommitError
whenever the changeset touches a file under .github/workflows/. The
existing swallow was intended for the narrow case of an App token
missing the 'workflows' scope, but it also masks unrelated ruleset
violations such as:
- GITHUB PUSH PROTECTION 'File path is restricted'
- Secret scanning push protection
- Signed commit / branch-naming / tag-deletion rulesets
Self-hosted operators cannot see these failures in aggregated log
reports because Renovate logs at info level, marks the branch as
aborted, and continues. Repositories that are effectively 100%
blocked by a ruleset appear healthy.
Detect the 'GH013' token before the workflow-files silent-swallow and
throw a CONFIG_VALIDATION error - mirroring the treatment already
applied to GH003 (force-push denied) and 'protected branch hook
declined'. This flags the repository as failing, populates
validationError / validationMessage so the failure reason is visible
in the repository summary, and lets any downstream reporting pick it
up as a real failure instead of an info-level abort.
Adds tests to lib/util/git/errors.spec.ts covering:
1. A GH013 Push Protection error whose changeset is entirely under
.github/workflows/ is thrown as CONFIG_VALIDATION with the
expected validationSource / validationError / validationMessage.
2. A generic non-GH013 workflow-file remote rejection still returns
null (existing swallow behaviour preserved).
91 lines
3.7 KiB
TypeScript
91 lines
3.7 KiB
TypeScript
import { codeBlock } from 'common-tags';
|
|
import { CONFIG_VALIDATION } from '../../constants/error-messages.ts';
|
|
import { bulkChangesDisallowed, handleCommitError } from './error.ts';
|
|
import type { FileChange } from './types.ts';
|
|
|
|
const errorMsg = codeBlock`
|
|
To https://github.com/the-org/st-mono.git
|
|
!\t:refs/renovate/branches/renovate/foo\t[remote failure] (remote failed to report status)
|
|
!\t:refs/renovate/branches/renovate/bar\t[remote failure] (remote failed to report status)
|
|
Done
|
|
Pushing to https://github.com/foo/bar.git
|
|
POST git-receive-pack (1234 bytes)
|
|
remote: Repository policies do not allow pushes that update more than 2 branches or tags.
|
|
error: failed to push some refs to 'https://github.com/foo/bar.git'
|
|
`;
|
|
|
|
// Real-world GH013 error from a GitHub Push Protection ruleset that
|
|
// restricts writes to files under .github/workflows/. Captured from a
|
|
// self-hosted Renovate run.
|
|
const gh013PushProtectionErr = codeBlock`
|
|
To https://github.com/the-org/example.git
|
|
! refs/renovate/branches/renovate/go-1.x:refs/renovate/branches/renovate/go-1.x [remote rejected] (push declined due to repository rule violations)
|
|
Done
|
|
Pushing to https://github.com/the-org/example.git
|
|
POST git-receive-pack (734 bytes)
|
|
remote: error: GH013: Repository rule violations found for refs/renovate/branches/renovate/go-1.x.
|
|
remote: Review all repository rules at https://github.com/the-org/example/rules?ref=refs%2Frenovate%2Fbranches%2Frenovate%2Fgo-1.x
|
|
remote:
|
|
remote: - GITHUB PUSH PROTECTION
|
|
remote: ---------------------------------------
|
|
remote: Resolve the following violations before pushing again
|
|
remote:
|
|
remote: - File path is restricted
|
|
remote: Found 2 violations:
|
|
remote:
|
|
remote: .github/workflows/build.yaml
|
|
remote: .github/workflows/deploy.yaml
|
|
remote:
|
|
error: failed to push some refs to 'https://github.com/the-org/example.git'
|
|
`;
|
|
|
|
const workflowFile: FileChange = {
|
|
type: 'addition',
|
|
path: '.github/workflows/build.yaml',
|
|
contents: '',
|
|
};
|
|
|
|
describe('util/git/errors', () => {
|
|
describe('bulkChangesDisallowed', () => {
|
|
it('should match the expected error', () => {
|
|
const err = new Error(errorMsg);
|
|
expect(bulkChangesDisallowed(err)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('handleCommitError', () => {
|
|
it('throws a CONFIG_VALIDATION error when GitHub returns GH013', () => {
|
|
const err = new Error(gh013PushProtectionErr);
|
|
// Even when every changed file is under .github/workflows/, which
|
|
// previously triggered a silent info-level abort, GH013 must be
|
|
// surfaced so the repository is flagged as failing.
|
|
let thrown: any;
|
|
try {
|
|
handleCommitError(err, 'renovate/go-1.x', [workflowFile]);
|
|
} catch (e) {
|
|
thrown = e;
|
|
}
|
|
expect(thrown).toBeDefined();
|
|
expect(thrown.message).toBe(CONFIG_VALIDATION);
|
|
expect(thrown.validationSource).toBe('renovate/go-1.x');
|
|
expect(thrown.validationError).toBe(
|
|
'GitHub repository ruleset violation',
|
|
);
|
|
expect(thrown.validationMessage).toContain('GH013');
|
|
expect(thrown.validationMessage).toContain('renovate/go-1.x');
|
|
});
|
|
|
|
it('still silently aborts on generic workflow-file push rejection', () => {
|
|
// Sanity check: non-GH013 workflow rejection should still be
|
|
// swallowed so we do not regress the existing behaviour where
|
|
// App tokens without the `workflows` scope abort quietly.
|
|
const err = new Error(codeBlock`
|
|
remote rejected refs/renovate/branches/foo
|
|
error: failed to push some refs to 'https://github.com/the-org/example.git'
|
|
`);
|
|
const result = handleCommitError(err, 'renovate/foo', [workflowFile]);
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
});
|