Skip to content

fix: release-please repeatedly force-pushes duplicate commits to open release PRs when no changes occurred on main #2881

Description

@quirogas

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

  1. 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.

  2. 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.

  3. 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

  1. Normalize Line Endings & Whitespace:
    In `Manifest.maybeUpdateExistingPullRequest`, normalize line endings (`\r\n` -> `\n`) and trim whitespace before comparing `existing.body` with `pullRequest.body`.
  2. 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.
  3. 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`.

Metadata

Metadata

Assignees

Labels

priority: p1Important issue which blocks shipping the next release. Will be fixed prior to next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions