Skip to content

Add docs-changelog-commit and docs-changelog-generate workflows and accompanying actions#21

Open
cotti wants to merge 24 commits intomainfrom
action/changelog
Open

Add docs-changelog-commit and docs-changelog-generate workflows and accompanying actions#21
cotti wants to merge 24 commits intomainfrom
action/changelog

Conversation

@cotti
Copy link

@cotti cotti commented Feb 27, 2026

Summary

Add changelog automation for pull requests, powered by docs-builder CLI commands.

When a PR is opened, labeled, or has its title edited, this automation generates a changelog YAML file based on the PR metadata and label-to-type mappings defined in docs/changelog.yml. The generated file is committed to the PR branch (or posted as a comment, depending on configuration).

What's included

Two composite actions:

  • changelog/generate — Runs in read-only PR context. Evaluates PR metadata against the changelog config (docs-builder changelog evaluate-pr), generates a changelog entry (docs-builder changelog add), and uploads the result as an artifact (docs-builder changelog prepare-artifact).
  • changelog/submit — Runs with write permissions via workflow_run. Downloads the artifact, re-validates PR state (docs-builder changelog evaluate-artifact), and either commits the file to the PR branch or posts it as a PR comment.

Two reusable workflows:

  • .github/workflows/changelog-generate.yml
  • .github/workflows/changelog-submit.yml

Adopting repositories call these reusable workflows — they don't need to reference the composite actions directly.

Three PR comment scripts:

  • post-success-comment.js — Links to the committed changelog file with view/edit URLs.
  • post-comment-only.js — Renders the changelog content inline as a code block (for forks or opt-in comment-only mode).
  • post-failure-comment.js — Lists available type labels when no matching label is found.

Design decisions

  • Two-workflow split for fork safety. The generate workflow runs with contents: read from the PR context. The submit workflow runs from workflow_run with write permissions on the base branch. This follows the standard pattern for preventing privilege escalation from fork PRs. Fork PRs automatically fall back to comment-only mode.
  • Delegates logic to docs-builder. PR evaluation, artifact preparation, and artifact validation are handled by three new docs-builder CLI commands (evaluate-pr, prepare-artifact, evaluate-artifact). The action YAML is a thin orchestration layer — it passes GitHub context to the CLI and routes outputs to the appropriate steps.
  • Config-driven rules. Skip labels, include/exclude semantics, per-product overrides, and match modes are all driven by rules.create in docs/changelog.yml. Nothing is hardcoded in the actions. See Rules for creation and publishing.
  • SHA verification before commit. After checking out the PR branch, the submit action verifies that HEAD matches the expected SHA from the artifact metadata. This prevents writing to the wrong branch if the ref has drifted.
  • Manual edit detection. If a human edits the changelog file directly (last commit author is not github-actions[bot]), the automation skips regeneration to preserve manual changes.
  • Idempotent PR comments. All comment scripts find and update an existing bot comment rather than creating duplicates, using github-actions[bot] login filtering and per_page: 100 pagination.

New files

File Purpose
changelog/generate/action.yml Composite action: evaluate PR, generate changelog, upload artifact
changelog/submit/action.yml Composite action: download artifact, validate, commit or comment
changelog/submit/scripts/post-success-comment.js PR comment with links to committed file
changelog/submit/scripts/post-comment-only.js PR comment with inline changelog content
changelog/submit/scripts/post-failure-comment.js PR comment listing available type labels
.github/workflows/changelog-generate.yml Reusable workflow for generate step
.github/workflows/changelog-submit.yml Reusable workflow for submit step
changelog/README.md Setup guide and usage documentation

Dependencies

This PR depends on three new docs-builder CLI commands:

  • docs-builder changelog evaluate-pr
  • docs-builder changelog prepare-artifact
  • docs-builder changelog evaluate-artifact

These are being added in a separate docs-builder PR.

How to adopt

  1. Run docs-builder changelog init in your repo to create docs/changelog.yml.
  2. Add the two workflow files referencing the reusable workflows.
  3. Ensure GitHub labels from your config exist in the repository.

See changelog/README.md for full setup instructions.

@cotti cotti self-assigned this Feb 27, 2026
@cotti cotti added the enhancement New feature or request label Feb 27, 2026
@cotti cotti added enhancement New feature or request and removed enhancement New feature or request labels Mar 2, 2026
@cotti cotti requested review from a team and reakaleek March 3, 2026 09:57
Copy link
Member

@reakaleek reakaleek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should put these actions in a top-level changelog folder.

IMO, that the commands are based on docs-builder is an implementation detail that could change in the future.

Nevertheless, GJ.

group: changelog-commit-${{ github.event.workflow_run.head_branch || github.run_id }}
cancel-in-progress: true

jobs:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, I would like to see a reusable workflow, where you can add in input and decide, if you want a commit or a comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not saying you need the comment logic today. But the ability to easily extend it without the need to change the consumer workflow.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gonna test a bit more on the playground to make sure it's ok, but I believe it's done.

@cotti cotti requested a review from reakaleek March 3, 2026 14:52
@Mpdreamz
Copy link
Member

Mpdreamz commented Mar 3, 2026

@cotti can you add to the PR what a repository needs to do to onboard?

#21

Seems to be using the shared workflows, how does the composite action come into play?

@Mpdreamz
Copy link
Member

Mpdreamz commented Mar 3, 2026

The changelog we commit should be minimal: https://github.com/elastic/docs-eng-playground/pull/48/changes/9d141c10e4994b69e91454bcf059b0045be34a0f

We can embedd a single line comment to the docs perhaps?

@@ -0,0 +1,38 @@
name: Changelog
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: Changelog
name: Changelog generate

on:
workflow_call:
inputs:
config:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our onboarding guides should leave this out, treat as expert option.

https://github.com/elastic/docs-eng-playground/blob/main/.github/workflows/docs-changelog-generate.yml#L22

Still sets it explicitly

description: 'Path to changelog.yml configuration file'
type: string
default: 'docs/changelog.yml'
strip-title-prefix:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this default is different from the CLI?

I think it makes sense for this to be true? cc @lcawl

description: 'Remove [Prefix]: from PR titles'
type: boolean
default: false
changelog-dir:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be settable here, folks need to use the config to set this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, folks can configure the changelog directory in the config file's bundle.directory per https://github.com/elastic/docs-builder/blob/main/config/changelog.example.yml

--output /tmp/changelog-staging \
--config "$CONFIG_FILE" \
--title "$PR_TITLE" \
--type "$PR_TYPE"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this, let changelog add do the logic to figure it out.

github-token:
description: 'GitHub token with contents:write and pull-requests:write'
default: '${{ github.token }}'
artifact-name:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be configurable? its always a fixed name no?

commit it to the PR branch (or post as comment), and update the PR.

inputs:
run-id:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be configurable? e.g when do we not want the default?

mkdir -p "$CHANGELOG_DIR"
cp "/tmp/changelog-result/${PR_NUMBER}.yaml" "$CHANGELOG_FILE"

git config user.name "github-actions[bot]"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do these need to be set? is it not the default already?

// Runs before any expensive operations (checkout, docs-builder setup).
module.exports = async ({ github, context, core }) => {
const labels = context.payload.pull_request.labels.map(l => l.name);
if (labels.includes('changelog:skip')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not universal, we need to flow this through docs-builder who can read the config


const labels = pr.labels.map(l => l.name);
if (labels.includes('changelog:skip')) {
core.info('changelog:skip label found on PR, aborting.');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the rules.create might be "only create changelog if xxx labels are present" (include option) or else "only create changelog if XXX labels are NOT present (exclude option). Likewise there are rules that apply only for specific product values. I think this action needs to handle those situations too.


### 3. Create the labels

Make sure the GitHub labels referenced in your `docs/changelog.yml` exist in your repository. You also need a `changelog:skip` label for PRs that should not generate a changelog entry.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Make sure the GitHub labels referenced in your `docs/changelog.yml` exist in your repository. You also need a `changelog:skip` label for PRs that should not generate a changelog entry.
Make sure the GitHub labels referenced in your `docs/changelog.yml` exist in your repository.
You also need a labelling strategy for situations where pull requests should not generate a changelog entry.

At the moment, we can refer them to https://elastic.github.io/docs-builder/contribute/changelog/#rules-for-creation-and-publishing for more info, though I'm updating that path in one of my outstanding PRs.

v
generate workflow (read-only)
|
+-- skip if changelog:skip label is present
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per my earlier comment, this should ideally support both include/exclude rules:

Suggested change
+-- skip if changelog:skip label is present
+-- skip if "exclude" labels are present (or "include" labels are absent, depending on label strategy)


## Skipping changelog generation

Add the `changelog:skip` label to a PR to skip changelog generation entirely. The generate action will exit early and no artifact or commit is produced.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Add the `changelog:skip` label to a PR to skip changelog generation entirely. The generate action will exit early and no artifact or commit is produced.
Optionally add labels to a PR to skip changelog generation entirely. The generate action will exit early and no artifact or commit is produced.

Per my other comments, this needs to handle all the options we provide in https://elastic.github.io/docs-builder/contribute/changelog/#rules-reference


## Output

Each PR produces a file at `docs/changelog/{PR_NUMBER}.yaml` on the PR branch. These files are consumed by `docs-builder` during documentation builds to produce a rendered changelog page.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each PR produces a file at docs/changelog/{PR_NUMBER}.yaml on the PR branch. These files are consumed by docs-builder during documentation builds to produce a rendered changelog page.

Per Martijn's comments elsewhere, I think this needs to accommodate the different file output names (https://elastic.github.io/docs-builder/contribute/changelog/#filenames) and file output directories (per config file). IMO we should expect them to have those choices defined in the config file rather than trying to support via command options.

@lcawl
Copy link

lcawl commented Mar 4, 2026

I added a few comments, but one thing I couldn't tell from perusing the PR was where/if the --products are set. Per https://elastic.github.io/docs-builder/cli/release/changelog-add/, --products is currently required.

Questions:

Per comments in our stand-up today, this might be a reason to quickly add support for "label -> product" mappings in the changelog configuration file so that this can optionally be derived similar to how we're deriving type, area, etc.
Perhaps the "changelog add" command should ultimately behave something like this (if it doesn't already):

  1. If --products is specified, use it.
  2. Otherwise, if there's a "label -> product" mapping in the changelog config and the command is fetching info from GitHub (via --prs or --issues functionality), derive it that way.
  3. Otherwise, check for product.default in config file.
  4. Otherwise, if products.available has only a single option, use that.
  5. Otherwise, fail? Or create the changelog with the products commented out (and validate that it's not left commented out permanently as part of https://github.com/elastic/docs-eng-team/issues/362)

@cotti cotti requested review from Mpdreamz and reakaleek March 6, 2026 18:22
Copy link
Member

@reakaleek reakaleek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, when comments from @lcawl and @Mpdreamz are resolved.

CONFIG_FILE: ${{ inputs.config }}
run: |
mkdir -p /tmp/changelog-staging
docs-builder changelog add \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want this command to generate PR-number filenames, it'll also need the --use-pr-number option per https://docs-v3-preview.elastic.dev/elastic/docs-builder/tree/main/cli/release/changelog-add. By default the command creates the kind of filenames Elastic Agent currently uses (timestamp plus title) but I suspect most teams will choose to use PR numbers like Elasticsearch does.
We don't currently have a way to set this in the changelog config, so want to leave it configurable we can add that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, thank you!

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds fork-safe changelog automation for PRs by introducing reusable workflows and composite actions that generate a changelog YAML entry via docs-builder, then either commit it to the PR branch or post it as an idempotent PR comment.

Changes:

  • Added composite actions for generating a changelog artifact and submitting it (commit/comment) based on validated PR state.
  • Added three GitHub Script helpers to post/update PR comments for success, comment-only mode, and “no matching label” failures.
  • Added reusable workflows and documentation to support adoption by downstream repositories.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
changelog/generate/action.yml Composite action to evaluate PR metadata, generate YAML, and upload artifact.
changelog/submit/action.yml Composite action to download artifact, re-validate, commit to PR branch or comment, and post status comments.
changelog/submit/scripts/post-success-comment.js Posts/updates PR comment linking to committed changelog file.
changelog/submit/scripts/post-comment-only.js Posts/updates PR comment containing the YAML content inline.
changelog/submit/scripts/post-failure-comment.js Posts/updates PR comment guiding label selection when no type matches.
.github/workflows/changelog-generate.yml Reusable workflow wrapper for the generate composite action.
.github/workflows/changelog-submit.yml Reusable workflow wrapper for the submit composite action.
changelog/README.md Adoption/setup docs explaining the two-workflow model and configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +26
const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c =>
c.user?.login === 'github-actions[bot]' &&
c.body?.startsWith(title)
);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script only fetches the first 100 PR comments. If a PR has more than 100 comments and the bot comment is older, existing may not be found and the action will create a duplicate comment instead of updating. Use Octokit pagination (e.g., github.paginate) or loop through pages until the matching comment is found.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +38
const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c =>
c.user?.login === 'github-actions[bot]' &&
c.body?.startsWith(title)
);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script only fetches the first 100 PR comments. On PRs with >100 comments, the existing bot comment may not be returned and the action can create duplicates. Consider using Octokit pagination (github.paginate) or iterating pages until the existing comment is found.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +39
const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c =>
c.user?.login === 'github-actions[bot]' &&
c.body?.startsWith(title)
);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script only fetches the first 100 PR comments. If the bot comment is not in the first page, this will post a new comment rather than updating, breaking the “idempotent” behavior. Use Octokit pagination (github.paginate) or page through results until the matching comment is found.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +42
- name: Download artifact
id: download
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: changelog-result
path: /tmp/changelog-result
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ inputs.github-token }}

- name: Setup docs-builder
if: steps.download.outcome == 'success'
uses: elastic/docs-actions/docs-builder/setup@v1
with:
version: latest
github-token: ${{ inputs.github-token }}

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actions/download-artifact is marked continue-on-error: true, but if the artifact download fails the rest of the action is skipped and the workflow still succeeds without leaving any signal (no failure, no PR comment). This will make misconfigurations or transient artifact issues hard to detect; consider failing the action when download fails, or adding an explicit step that reports the failure (and optionally comments) when steps.download.outcome != 'success'.

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +13
github-token:
description: 'GitHub token with contents:write and pull-requests:write'
default: '${{ github.token }}'
comment-only:
description: 'Post changelog as a PR comment instead of committing to the branch'
default: 'false'
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The github-token input description mentions contents:write and pull-requests:write, but this action also needs actions:read to download artifacts from the generating workflow run. Updating the description (or documenting required permissions) would prevent confusing “artifact not found/unauthorized” failures for adopters.

Copilot uses AI. Check for mistakes.
jobs:
generate:
if: github.event.pull_request.state == 'open'
runs-on: ubuntu-latest
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reusable workflow doesn’t set explicit permissions. If an adopting repo relies on the default GITHUB_TOKEN permissions, the generate job could run with broader rights than intended in PR context. Consider declaring minimal permissions here (e.g., contents: read plus any required read scopes) so the called workflow enforces least-privilege even when callers forget to set permissions.

Suggested change
runs-on: ubuntu-latest
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +20
name: Changelog submit

on:
workflow_call:
inputs:
comment-only:
description: 'Post changelog as a PR comment instead of committing to the branch'
type: boolean
default: false

concurrency:
group: changelog-submit-${{ github.event.workflow_run.head_branch || github.run_id }}
cancel-in-progress: true

jobs:
submit:
if: >
github.event.workflow_run.event == 'pull_request'
&& github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reusable workflow doesn’t declare permissions, even though it needs at least actions: read to download artifacts and typically contents: write / pull-requests: write to commit and comment. Adding an explicit permissions block here enforces least privilege and makes adoption less error-prone (callers can still further restrict but not expand).

Copilot uses AI. Check for mistakes.
@cotti cotti force-pushed the action/changelog branch from ebadc2f to 6e8d205 Compare March 11, 2026 01:00
@cotti cotti force-pushed the action/changelog branch from 6e8d205 to a3c1f04 Compare March 11, 2026 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants