From c810ad5a5acf8c93bcbb0b0ee80025ae883d1622 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 3 Aug 2026 16:51:46 +0200 Subject: [PATCH 1/3] fix(generate-changelog): do not fail on release commits or empty changelogs The generate_changelog workflow fires on every push to main, including when a release PR is merged. Commitizen then has nothing to add to the changelog and exits non-zero, failing the job. Two variants have been observed in dfinity/icp-js-core: - exit 3 ("No commits found") when the tag already exists - exit 16 ("No tag found to do an incremental changelog") when the tag has not been pushed yet, which is the common case since the release tag is pushed manually a couple of minutes after the release PR merges Both are no-ops in intent, not errors. Two complementary changes: - Skip the changelog steps entirely when the head commit subject looks like a release commit, via a new release_commit_pattern input. This covers the exit 16 case, whose cause is the tag not existing yet. - Treat commitizen exit 3 as success in actions/generate-changelog, so a release commit that slips past the subject check (retitled release PR, or a repo that permits merge commits) still does not fail the job. Exit 16 deliberately remains fatal. Unlike exit 3 it is ambiguous: it also fires when tags are genuinely unavailable, for example a shallow clone or a regression in tag fetching. Swallowing it would turn a loud failure into a silent no-op where the changelog quietly stops being generated. Closes #75 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/generate-changelog.yaml | 28 +++++++++++++++++++++++ actions/generate-changelog/action.yaml | 15 +++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate-changelog.yaml b/.github/workflows/generate-changelog.yaml index 75caf29..61497b6 100644 --- a/.github/workflows/generate-changelog.yaml +++ b/.github/workflows/generate-changelog.yaml @@ -48,6 +48,12 @@ on: default: 'chore: generate changelog' type: string + release_commit_pattern: + description: 'Skip changelog generation when the head commit subject matches this extended regular expression. Set to an empty string to disable the check.' + required: false + default: '^chore:[[:space:]]release([[:space:]]|$)' + type: string + token_app_id: description: 'A GitHub App ID used to generate an access token to create a pull request.' required: true @@ -94,18 +100,40 @@ jobs: fetch-depth: 0 fetch-tags: true + - name: Check for release commit + id: release_commit + shell: bash + env: + RELEASE_COMMIT_PATTERN: ${{ inputs.release_commit_pattern }} + run: | + subject=$(git log -1 --format=%s) + + # A release commit bumps the project version, and the matching tag is usually not + # pushed yet when this workflow runs. Commitizen then has nothing to changelog and + # exits non-zero, failing the job. Skip those commits entirely. + if [ -n "$RELEASE_COMMIT_PATTERN" ] && [[ "$subject" =~ $RELEASE_COMMIT_PATTERN ]]; then + echo 'skip=true' >> "$GITHUB_OUTPUT" + echo "::notice::Head commit is a release commit ('$subject'); skipping changelog generation." + else + echo 'skip=false' >> "$GITHUB_OUTPUT" + fi + - name: Setup Python + if: steps.release_commit.outputs.skip != 'true' uses: dfinity/ci-tools/actions/setup-python@afeee4fbdc0683a88ec5a74ed7f59a2ce0e833ad # main - name: Setup Commitizen + if: steps.release_commit.outputs.skip != 'true' uses: dfinity/ci-tools/actions/setup-commitizen@afeee4fbdc0683a88ec5a74ed7f59a2ce0e833ad # main - name: Generate changelog + if: steps.release_commit.outputs.skip != 'true' uses: dfinity/ci-tools/actions/generate-changelog@afeee4fbdc0683a88ec5a74ed7f59a2ce0e833ad # main with: file_name: ${{ inputs.file_name }} - name: Create pull request + if: steps.release_commit.outputs.skip != 'true' uses: dfinity/ci-tools/actions/create-pr@afeee4fbdc0683a88ec5a74ed7f59a2ce0e833ad # main with: branch_name: ${{ inputs.branch_name }} diff --git a/actions/generate-changelog/action.yaml b/actions/generate-changelog/action.yaml index d34be07..233a6a6 100644 --- a/actions/generate-changelog/action.yaml +++ b/actions/generate-changelog/action.yaml @@ -14,4 +14,17 @@ runs: shell: bash env: FILE_NAME: ${{ inputs.file_name }} - run: cz changelog --incremental --merge-prerelease --file-name="$FILE_NAME" --version-scheme semver2 + run: | + set +e + cz changelog --incremental --merge-prerelease --file-name="$FILE_NAME" --version-scheme semver2 + exit_code=$? + set -e + + # Commitizen exits 3 when it finds no commits to add to the changelog. That is a + # no-op rather than a failure, so it must not fail the job. + if [ "$exit_code" -eq 3 ]; then + echo "::notice::No commits to add to the changelog; nothing to do." + exit 0 + fi + + exit "$exit_code" From f1939a439a7d5b13d94be83e59151eb8d157154e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 17 Sep 2026 10:02:50 +0200 Subject: [PATCH 2/3] build(setup-commitizen): pin the Commitizen version The changelog workflow now treats exit code 3 as a no-op, so the tool defining that code should not upgrade itself on every run. Verified against the pinned version that 3 is NO_COMMITS_FOUND. Also documents release_commit_pattern, which the workflow README was missing. Co-Authored-By: Claude Opus 5 (1M context) --- actions/setup-commitizen/README.md | 2 ++ actions/setup-commitizen/action.yaml | 6 ++++-- workflows/generate-changelog/README.md | 23 +++++++++++++---------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/actions/setup-commitizen/README.md b/actions/setup-commitizen/README.md index 8bed46b..ff64d85 100644 --- a/actions/setup-commitizen/README.md +++ b/actions/setup-commitizen/README.md @@ -2,6 +2,8 @@ This actions sets up [Commitizen](https://commitizen-tools.github.io/commitizen/) for use in actions. It assumes [Python](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/) are already setup, see the [setup Python action](../setup-python/README.md) for a ready to use action to do this. +The Commitizen version is pinned in `action.yaml`, the same way the [setup Python action](../setup-python/README.md) pins Python. The [generate changelog workflow](../../workflows/generate-changelog/README.md) treats specific Commitizen exit codes as a no-op rather than a failure, so the version it runs against should change deliberately. Bump the pin in `action.yaml` to upgrade. + ## Example usage ```yaml diff --git a/actions/setup-commitizen/action.yaml b/actions/setup-commitizen/action.yaml index 72c90c4..a316d4b 100644 --- a/actions/setup-commitizen/action.yaml +++ b/actions/setup-commitizen/action.yaml @@ -4,6 +4,8 @@ description: 'This action sets up Commitizen for use in actions.' runs: using: composite steps: - - name: Install Commitzen + # Pinned rather than upgraded on every run: the changelog workflow relies on + # Commitizen's exit codes to tell a no-op apart from a real failure. + - name: Install Commitizen shell: bash - run: pip install -U Commitizen + run: pip install 'commitizen==4.18.1' diff --git a/workflows/generate-changelog/README.md b/workflows/generate-changelog/README.md index ea0482d..23509dd 100644 --- a/workflows/generate-changelog/README.md +++ b/workflows/generate-changelog/README.md @@ -8,18 +8,21 @@ Any files that will be changed and committed to the pull request must be listed CHANGELOG.md ``` +Release commits are skipped. A release bumps the project version without adding anything a changelog entry could be generated from, so Commitizen has nothing to do and exits non-zero. Both that and an empty changelog are treated as a no-op rather than a failure. + ## Workflow inputs -| Input | Description | Default | -| -------------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------- | -| `branch_name` | The name of the branch to create the pull request from. | `'patch'` | -| `base_branch_name` | The name of the base branch to create a pull request against. | `'main'` | -| `pull_request_title` | The title of the pull request. | `'chore: automated by GitHub actions'` | -| `pull_request_body` | The body of the pull request. | `'This pull request was automatically created by a GitHub Action.'` | -| `author_name` | The name of the author of the pull request and commit. | `${{ github.actor }}` | -| `author_email` | The email of the author of the pull request and commit. | `${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com` | -| `commit_message` | The message of the commit. | `'chore: automated by GitHub actions'` | -| `token_app_id` | A GitHub App ID used to generate an access token to create a pull request. | _required_ | +| Input | Description | Default | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `branch_name` | The name of the branch to create the pull request from. | `'patch'` | +| `base_branch_name` | The name of the base branch to create a pull request against. | `'main'` | +| `pull_request_title` | The title of the pull request. | `'chore: automated by GitHub actions'` | +| `pull_request_body` | The body of the pull request. | `'This pull request was automatically created by a GitHub Action.'` | +| `author_name` | The name of the author of the pull request and commit. | `${{ github.actor }}` | +| `author_email` | The email of the author of the pull request and commit. | `${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com` | +| `commit_message` | The message of the commit. | `'chore: automated by GitHub actions'` | +| `release_commit_pattern` | Skip changelog generation when the head commit subject matches this extended regular expression. Set to an empty string to disable the check. | `'^chore:[[:space:]]release([[:space:]]\|$)'` | +| `token_app_id` | A GitHub App ID used to generate an access token to create a pull request. | _required_ | ## Workflow secrets From 20889b436a3a1a6e8e545b4c789597eae0ca73b0 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 17 Sep 2026 12:17:51 +0200 Subject: [PATCH 3/3] fix(generate-changelog): fail on an invalid release commit pattern Bash returns 2 rather than 1 for a malformed regular expression, and as an if condition that fell through to the else branch and recorded skip=false. A typo in the pattern therefore disabled the release check while the job stayed green. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/generate-changelog.yaml | 25 ++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/generate-changelog.yaml b/.github/workflows/generate-changelog.yaml index 61497b6..5da7429 100644 --- a/.github/workflows/generate-changelog.yaml +++ b/.github/workflows/generate-changelog.yaml @@ -107,17 +107,32 @@ jobs: RELEASE_COMMIT_PATTERN: ${{ inputs.release_commit_pattern }} run: | subject=$(git log -1 --format=%s) + skip=false # A release commit bumps the project version, and the matching tag is usually not # pushed yet when this workflow runs. Commitizen then has nothing to changelog and # exits non-zero, failing the job. Skip those commits entirely. - if [ -n "$RELEASE_COMMIT_PATTERN" ] && [[ "$subject" =~ $RELEASE_COMMIT_PATTERN ]]; then - echo 'skip=true' >> "$GITHUB_OUTPUT" - echo "::notice::Head commit is a release commit ('$subject'); skipping changelog generation." - else - echo 'skip=false' >> "$GITHUB_OUTPUT" + if [ -n "$RELEASE_COMMIT_PATTERN" ]; then + set +e + [[ "$subject" =~ $RELEASE_COMMIT_PATTERN ]] + match=$? + set -e + + # A malformed pattern returns 2 rather than 1. Treated as "no match" it would + # disable the check while the job stayed green, so fail on it instead. + if [ "$match" -eq 2 ]; then + echo "::error::release_commit_pattern is not a valid extended regular expression: $RELEASE_COMMIT_PATTERN" + exit 1 + fi + + if [ "$match" -eq 0 ]; then + skip=true + echo "::notice::Head commit is a release commit ('$subject'); skipping changelog generation." + fi fi + echo "skip=$skip" >> "$GITHUB_OUTPUT" + - name: Setup Python if: steps.release_commit.outputs.skip != 'true' uses: dfinity/ci-tools/actions/setup-python@afeee4fbdc0683a88ec5a74ed7f59a2ce0e833ad # main