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).
162 lines
6.0 KiB
TypeScript
162 lines
6.0 KiB
TypeScript
import { CONFIG_VALIDATION } from '../../constants/error-messages.ts';
|
|
import { logger } from '../../logger/index.ts';
|
|
import { ExternalHostError } from '../../types/errors/external-host-error.ts';
|
|
import { getEnv } from '../env.ts';
|
|
import type { FileChange } from './types.ts';
|
|
|
|
export function checkForPlatformFailure(err: Error): Error | null {
|
|
if (getEnv().NODE_ENV === 'test') {
|
|
return null;
|
|
}
|
|
const externalHostFailureStrings = [
|
|
'remote: Invalid username or password',
|
|
'gnutls_handshake() failed',
|
|
'The requested URL returned error: 403',
|
|
'The requested URL returned error: 5',
|
|
'The remote end hung up unexpectedly',
|
|
'access denied or repository not exported',
|
|
'Could not write new index file',
|
|
'Failed to connect to',
|
|
'Connection timed out',
|
|
'malformed object name',
|
|
'Could not resolve host',
|
|
'early EOF',
|
|
'fatal: bad config', // .gitmodules problem
|
|
'expected flush after ref listing',
|
|
];
|
|
for (const errorStr of externalHostFailureStrings) {
|
|
if (err.message.includes(errorStr)) {
|
|
logger.debug({ err }, 'Converting git error to ExternalHostError');
|
|
return new ExternalHostError(err, 'git');
|
|
}
|
|
/* v8 ignore next -- TODO: add test */
|
|
}
|
|
|
|
/* v8 ignore next -- TODO: add test */
|
|
const configErrorStrings = [
|
|
{
|
|
error: 'GitLab: Branch name does not follow the pattern',
|
|
message:
|
|
"Cannot push because branch name does not follow project's push rules",
|
|
},
|
|
{
|
|
error: 'GitLab: Commit message does not follow the pattern',
|
|
message:
|
|
"Cannot push because commit message does not follow project's push rules",
|
|
},
|
|
{
|
|
error: ' is not a member of team',
|
|
message:
|
|
'The `Restrict commits to existing GitLab users` rule is blocking Renovate push. Check the Renovate `gitAuthor` setting',
|
|
},
|
|
{
|
|
error: 'TF401027:',
|
|
message:
|
|
'You need the Git `GenericContribute` permission to perform this action',
|
|
},
|
|
{
|
|
error: 'matches more than one',
|
|
message:
|
|
"Renovate cannot push branches if there are tags with names the same as Renovate's branches. Please remove conflicting tag names or change Renovate's branchPrefix to avoid conflicts.",
|
|
},
|
|
];
|
|
/* v8 ignore next -- TODO: add test */
|
|
for (const { error, message } of configErrorStrings) {
|
|
if (err.message.includes(error)) {
|
|
logger.debug({ err }, 'Converting git error to CONFIG_VALIDATION error');
|
|
const res = new Error(CONFIG_VALIDATION);
|
|
res.validationError = message;
|
|
res.validationMessage = `\`${err.message.replaceAll('`', "'")}\``;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/* v8 ignore next -- TODO: add test */
|
|
return null;
|
|
}
|
|
|
|
/* v8 ignore next -- TODO: add tests */
|
|
export function handleCommitError(
|
|
err: Error,
|
|
branchName: string,
|
|
files?: FileChange[],
|
|
): null {
|
|
checkForPlatformFailure(err);
|
|
if (err.message.includes(`'refs/heads/renovate' exists`)) {
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
error.validationSource = 'None';
|
|
error.validationError = 'An existing branch is blocking Renovate';
|
|
error.validationMessage = `Renovate needs to create the branch \`${branchName}\` but is blocked from doing so because of an existing branch called \`renovate\`. Please remove it so that Renovate can proceed.`;
|
|
throw error;
|
|
}
|
|
if (
|
|
err.message.includes(
|
|
'refusing to allow a GitHub App to create or update workflow',
|
|
)
|
|
) {
|
|
logger.warn(
|
|
'App has not been granted permissions to update Workflows - aborting branch.',
|
|
);
|
|
return null;
|
|
}
|
|
if (err.message.includes('GH013')) {
|
|
logger.debug({ err }, 'GitHub repository ruleset blocked push');
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
error.validationSource = branchName;
|
|
error.validationError = 'GitHub repository ruleset violation';
|
|
error.validationMessage =
|
|
`Renovate cannot push to \`${branchName}\` because a GitHub ` +
|
|
`repository ruleset rejected the push (GH013). Update the ` +
|
|
`ruleset - or grant Renovate a bypass actor - so Renovate can ` +
|
|
`proceed. Original error: \`${err.message.replaceAll('`', "'")}\``;
|
|
throw error;
|
|
}
|
|
if (
|
|
(err.message.includes('remote rejected') || err.message.includes('403')) &&
|
|
files?.some((file) => file.path?.startsWith('.github/workflows/'))
|
|
) {
|
|
logger.debug({ err }, 'commitFiles error');
|
|
logger.info('Workflows update rejection - aborting branch.');
|
|
return null;
|
|
}
|
|
if (err.message.includes('protected branch hook declined')) {
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
error.validationSource = branchName;
|
|
error.validationError = 'Renovate branch is protected';
|
|
error.validationMessage = `Renovate cannot push to its branch because branch protection has been enabled.`;
|
|
throw error;
|
|
}
|
|
if (err.message.includes('can only push your own commits')) {
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
error.validationSource = branchName;
|
|
error.validationError = 'Bitbucket committer error';
|
|
error.validationMessage = `Renovate has experienced the following error when attempting to push its branch to the server: \`${err.message.replaceAll(
|
|
'`',
|
|
"'",
|
|
)}\``;
|
|
throw error;
|
|
}
|
|
if (err.message.includes('remote: error: cannot lock ref')) {
|
|
logger.error({ err }, 'Error committing files.');
|
|
return null;
|
|
}
|
|
if (
|
|
err.message.includes('denying non-fast-forward') ||
|
|
err.message.includes('GH003: Sorry, force-pushing')
|
|
) {
|
|
logger.debug({ err }, 'Permission denied to update branch');
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
error.validationSource = branchName;
|
|
error.validationError = 'Force push denied';
|
|
error.validationMessage = `Renovate is unable to update branch(es) due to force pushes being disallowed.`;
|
|
throw error;
|
|
}
|
|
logger.debug({ err }, 'Unknown error committing files');
|
|
// We don't know why this happened, so this will cause bubble up to a branch error
|
|
throw err;
|
|
}
|
|
|
|
export function bulkChangesDisallowed(err: Error): boolean {
|
|
return err.message.includes('update more than');
|
|
}
|