Environment details
Problem Description
In large monorepos (such as googleapis/google-cloud-python), release-please repeatedly force-pushes new commits to open release pull requests every 15–30 minutes, even when no new commits have merged into `main`.
Every force-push updates the `HEAD` ref of the release PR branch (`release-please--branches--`), which fires `pull_request.synchronize` webhooks. In repositories configured with GitHub Actions concurrency cancellation (`cancel-in-progress: true`), this repeatedly cancels in-flight CI runs and restarts them from scratch. Because full CI runs in large monorepos take 30–45 minutes, the continuous re-pushing prevents CI from ever finishing.
Root Cause Analysis
-
Brittle PR Body String Comparison in `Manifest.maybeUpdateExistingPullRequest`:
In `src/manifest.ts`:
private async maybeUpdateExistingPullRequest(
existing: PullRequest,
pullRequest: ReleasePullRequest
): Promise<PullRequest | undefined> {
// If unchanged, no need to push updates
if (existing.body === pullRequest.body.toString()) {
this.logger.info(
`PR https://github.com/${this.repository.owner}/${this.repository.repo}/pull/${existing.number} remained the same`
);
return undefined;
}
return await this.updateExistingPullRequest(existing, pullRequest);
}
`release-please` strictly compares raw string equality between `existing.body` (fetched from GitHub API) and `pullRequest.body.toString()`. GitHub API normalizes whitespace, line breaks (`\r\n` vs `\n`), and HTML comment formatting. This causes string equality to evaluate to `false` even when release notes and file contents are identical.
-
No Git Tree Idempotency in `code-suggester` (`commitAndPush`):
When `maybeUpdateExistingPullRequest` calls `updateExistingPullRequest`, `code-suggester` creates brand new commit objects on top of `main` without checking if the target branch (`release-please--branches--`) already points to the exact same git tree.
Because a new commit object is created with new timestamps, a new commit SHA is produced and `updateRef(..., force: true)` is executed, triggering GitHub push webhooks.
-
Multi-Commit Chunking Amplification:
`src/util/code-suggester/github/commit-and-push.ts` sets `DEFAULT_FILES_PER_COMMIT = 100`. For large bulk release PRs touching >100 files (e.g., 218 files across ~100 packages in PR #18013), each run creates 3 separate sequential commits (`chore: release main`), which amplifies webhook events and CI churn.
Proposed Solution
- Normalize Line Endings & Whitespace:
In `Manifest.maybeUpdateExistingPullRequest`, normalize line endings (`\r\n` -> `\n`) and trim whitespace before comparing `existing.body` with `pullRequest.body`.
- Decouple PR Metadata Updates from Git Ref Updates:
If the PR body or title has minor text formatting changes but the generated code tree is identical, update only the pull request description via `octokit.pulls.update(...)` without creating new git commits or updating git refs.
- Tree Idempotency in `commitAndPush`:
In `src/util/code-suggester/github/commit-and-push.ts`, inspect the current `HEAD` commit tree of the remote branch. If the tree SHA matches the newly generated tree SHA, skip `updateRef`.
Environment details
release-pleaseversion: v17.x / latest (andrelease-please-botinrepo-automation-bots)googleapis/google-cloud-pythonProblem Description
In large monorepos (such as
googleapis/google-cloud-python),release-pleaserepeatedly force-pushes new commits to open release pull requests every 15–30 minutes, even when no new commits have merged into `main`.Every force-push updates the `HEAD` ref of the release PR branch (`release-please--branches--`), which fires `pull_request.synchronize` webhooks. In repositories configured with GitHub Actions concurrency cancellation (`cancel-in-progress: true`), this repeatedly cancels in-flight CI runs and restarts them from scratch. Because full CI runs in large monorepos take 30–45 minutes, the continuous re-pushing prevents CI from ever finishing.
Root Cause Analysis
Brittle PR Body String Comparison in `Manifest.maybeUpdateExistingPullRequest`:
In `src/manifest.ts`:
`release-please` strictly compares raw string equality between `existing.body` (fetched from GitHub API) and `pullRequest.body.toString()`. GitHub API normalizes whitespace, line breaks (`\r\n` vs `\n`), and HTML comment formatting. This causes string equality to evaluate to `false` even when release notes and file contents are identical.
No Git Tree Idempotency in `code-suggester` (`commitAndPush`):
When `maybeUpdateExistingPullRequest` calls `updateExistingPullRequest`, `code-suggester` creates brand new commit objects on top of `main` without checking if the target branch (`release-please--branches--`) already points to the exact same git tree.
Because a new commit object is created with new timestamps, a new commit SHA is produced and `updateRef(..., force: true)` is executed, triggering GitHub push webhooks.
Multi-Commit Chunking Amplification:
`src/util/code-suggester/github/commit-and-push.ts` sets `DEFAULT_FILES_PER_COMMIT = 100`. For large bulk release PRs touching >100 files (e.g., 218 files across ~100 packages in PR #18013), each run creates 3 separate sequential commits (`chore: release main`), which amplifies webhook events and CI churn.
Proposed Solution
In `Manifest.maybeUpdateExistingPullRequest`, normalize line endings (`\r\n` -> `\n`) and trim whitespace before comparing `existing.body` with `pullRequest.body`.
If the PR body or title has minor text formatting changes but the generated code tree is identical, update only the pull request description via `octokit.pulls.update(...)` without creating new git commits or updating git refs.
In `src/util/code-suggester/github/commit-and-push.ts`, inspect the current `HEAD` commit tree of the remote branch. If the tree SHA matches the newly generated tree SHA, skip `updateRef`.