Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0e7290a
Add docs-changelog-commit and docs-changelog-generate workflows and a…
cotti Feb 27, 2026
490dab8
Add actions/checkout
cotti Feb 27, 2026
0f5eeb2
Use named refs
cotti Feb 27, 2026
feba56f
Remove workflow shims
cotti Feb 27, 2026
e90995f
Resolve PR metadata
cotti Feb 27, 2026
252a590
Some refactorings to reduce excess variables
cotti Mar 3, 2026
583ba51
Use the GH API to infer commit authorship
cotti Mar 3, 2026
60ebc8a
Streamline commit stage
cotti Mar 3, 2026
26b0adf
Separate commit messages for updates
cotti Mar 3, 2026
2966964
Minor adjustments to messages
cotti Mar 3, 2026
116c92f
Restructure into reusable workflows
cotti Mar 3, 2026
199da2d
Merge branch 'main' into action/changelog
cotti Mar 3, 2026
38575bc
Change fork PRs to comment mode by default
cotti Mar 3, 2026
5a7f7ce
Check for SHA matches prior to committing
cotti Mar 3, 2026
dce6d29
Re-run on edited titles
cotti Mar 3, 2026
d8faffe
Use custom delimiters and readjust metadata generation
cotti Mar 3, 2026
5f06856
[temporary]: set to branches for full workflow test
cotti Mar 3, 2026
981d2f8
Apply review suggestions
cotti Mar 4, 2026
b86ad95
Update actions to leverage docs-builder changelog commands
cotti Mar 6, 2026
2e7371a
Merge remote-tracking branch 'origin/main' into action/changelog
cotti Mar 6, 2026
f9a6523
Remove changelog-dir and run-id
cotti Mar 6, 2026
7385214
Add --concise flag, update README
cotti Mar 6, 2026
9e55057
Add env var input to changelog add
cotti Mar 11, 2026
b723a0c
Adjust to use filenames configured by the repo
cotti Mar 13, 2026
aad75f3
Merge branch 'main' into action/changelog
cotti Mar 13, 2026
d424f92
Update changelog/README.md
cotti Mar 17, 2026
76f2baa
Update changelog/README.md
cotti Mar 17, 2026
5daa6d1
Move most of the process to the submit workflow
cotti Mar 18, 2026
a48024a
Merge branch 'main' into action/changelog
cotti Mar 18, 2026
8028d84
Use pagination for listing comments
cotti Mar 19, 2026
fa5cce5
Allow reading changed config files only if it's not a forked PR
cotti Mar 19, 2026
a67c45a
Use sanitized config file path
cotti Mar 19, 2026
1d925fa
Limit token usage lifetime
cotti Mar 19, 2026
dd9ba2d
Use explicit permissions
cotti Mar 19, 2026
9d79b3c
Add null guard and pagination safeguards for PR resolution
cotti Mar 19, 2026
c7f963e
Adjust README
cotti Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/changelog-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Changelog commit
Comment thread
cotti marked this conversation as resolved.
Outdated

on:
workflow_call:
inputs:
run-id:
description: 'Workflow run ID to download the changelog artifact from'
type: string
required: true
comment-only:
description: 'Post changelog as a PR comment instead of committing to the branch'
type: boolean
default: false

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

jobs:
commit:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Commit changelog
uses: elastic/docs-actions/changelog/commit@v1
with:
run-id: ${{ inputs.run-id }}
comment-only: ${{ inputs.comment-only }}
38 changes: 38 additions & 0 deletions .github/workflows/changelog-generate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Changelog
Comment thread
cotti marked this conversation as resolved.
Outdated

on:
workflow_call:
inputs:
config:
description: 'Path to changelog.yml configuration file'
type: string
default: 'docs/changelog.yml'
strip-title-prefix:
Comment thread
cotti marked this conversation as resolved.
Outdated
description: 'Remove [Prefix]: from PR titles'
type: boolean
default: false
changelog-dir:
Comment thread
cotti marked this conversation as resolved.
Outdated
description: 'Directory for changelog entry files'
type: string
default: 'docs/changelog'
docs-builder-version:
Comment thread
cotti marked this conversation as resolved.
Outdated
description: 'docs-builder version to install'
type: string
default: 'edge'

concurrency:
group: changelog-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
generate:
if: github.event.pull_request.state == 'open'
runs-on: ubuntu-latest
steps:
- name: Generate changelog
uses: elastic/docs-actions/changelog/generate@v1
with:
config: ${{ inputs.config }}
strip-title-prefix: ${{ inputs.strip-title-prefix }}
changelog-dir: ${{ inputs.changelog-dir }}
docs-builder-version: ${{ inputs.docs-builder-version }}
167 changes: 167 additions & 0 deletions changelog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Changelog automation

Automatically generate and commit changelog entries for pull requests.

When a PR is opened or labeled, the system generates a changelog YAML file based on the PR title and type label, commits it to the PR branch, and posts a comment with a link to view or edit the entry.

## Setup

### 1. Add the changelog configuration

Create `docs/changelog.yml` in your repository by running `docs-builder changelog init`.

By default, you will get a file with this structure:

```yaml
pivot:
types:
enhancement:
labels:
- enhancement
- feature
bug:
labels:
- bug
breaking:
labels:
- breaking
deprecation:
labels:
- deprecation
```

Each key under `pivot.types` is a changelog type. The `labels` list defines which GitHub labels map to that type. When a PR has one of these labels, the changelog entry is categorized accordingly.

### 2. Create the workflow

Add the following:

**`.github/workflows/changelog-generate.yml`**

```yaml
name: changelog-generate

on:
pull_request:
types:
- opened
- synchronize
- reopened
- edited
- labeled
- unlabeled

permissions:
contents: read

jobs:
generate:
uses: elastic/docs-actions/.github/workflows/changelog-generate.yml@v1
```

**`.github/workflows/changelog-commit.yml`**

```yaml
name: changelog-commit

on:
workflow_run:
workflows: [changelog-generate]
types:
- completed

permissions:
actions: read
contents: write
pull-requests: write

jobs:
Comment thread
cotti marked this conversation as resolved.
commit:
uses: elastic/docs-actions/.github/workflows/changelog-commit.yml@v1
with:
run-id: ${{ github.event.workflow_run.id }}
```

> **Important:** The `name` in the generate workflow (`changelog-generate`) must match the `workflows:` reference in the commit workflow. If you rename one, rename the other.

The two-workflow design is required because the generate workflow runs with read-only permissions (from the PR context), while the commit workflow runs with write permissions (from `workflow_run`, which uses the base branch's permissions). This is a [standard pattern](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) for safely writing to PR branches from forks.

### 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
Copy Markdown
Contributor

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.


## How it works

```
PR opened/labeled/title edited
|
v
generate job
|
+-- skip if changelog:skip label is present
Copy link
Copy Markdown
Contributor

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)

+-- skip if last commit is from the bot (prevents loops)
+-- skip if changelog file was manually edited
|
+-- runs: docs-builder changelog add
+-- uploads result as artifact
|
v
commit job
|
+-- re-validates PR state (labels, head SHA)
+-- downloads artifact
+-- commits changelog file to PR branch
+-- posts PR comment with view/edit links
```

## Inputs

### Generate workflow

| Input | Description | Default |
|------------------------|------------------------------------------|----------------------|
| `config` | Path to changelog configuration file | `docs/changelog.yml` |
| `strip-title-prefix` | Remove `[Prefix]:` from PR titles | `false` |
| `changelog-dir` | Directory for changelog entry files | `docs/changelog` |
| `docs-builder-version` | docs-builder version to install | `edge` |

### Commit workflow

| Input | Description | Default |
|----------------|---------------------------------------------------------|----------|
| `run-id` | Workflow run ID to download the changelog artifact from | required |
| `comment-only` | Post as PR comment instead of committing to the branch | `false` |

### Comment-only mode

If you prefer not to have bot commits on your PR branches, pass `comment-only: true` to the commit workflow. The changelog entry will be posted as a PR comment instead of being committed to the branch:

```yaml
jobs:
commit:
uses: elastic/docs-actions/.github/workflows/changelog-commit.yml@v1
with:
run-id: ${{ github.event.workflow_run.id }}
comment-only: true
```

## 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
Copy Markdown
Contributor

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


## Manual edits

If a human edits the changelog file directly (i.e., the last commit to `docs/changelog/{PR_NUMBER}.yaml` is not from `github-actions[bot]`), the automation will not overwrite it. This lets authors customize the generated entry without it being regenerated on the next push.
Comment thread
cotti marked this conversation as resolved.
Outdated

## 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
Copy Markdown
Contributor

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.

Comment thread
cotti marked this conversation as resolved.
Outdated

## Advanced: using composite actions directly

The reusable workflows are thin wrappers around two composite actions. If you need more control, you can use them directly in your own workflow steps:

- `elastic/docs-actions/changelog/generate@v1` -- generates the changelog and uploads an artifact
- `elastic/docs-actions/changelog/commit@v1` -- downloads the artifact and commits or comments

See the individual action files for their full input/output definitions.
129 changes: 129 additions & 0 deletions changelog/commit/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: changelog/commit
description: >
Download a changelog artifact from a generate workflow run,
commit it to the PR branch (or post as comment), and update the PR.

inputs:
run-id:
description: 'Workflow run ID to download the changelog artifact from'
default: '${{ github.run_id }}'
github-token:
description: 'GitHub token with contents:write and pull-requests:write'
default: '${{ github.token }}'
artifact-name:
Comment thread
cotti marked this conversation as resolved.
Outdated
description: 'Name of the artifact to download'
default: 'changelog-result'
comment-only:
description: 'Post changelog as a PR comment instead of committing to the branch'
default: 'false'

outputs:
committed:
description: 'Whether a changelog was committed (true/false)'
value: ${{ steps.commit.outputs.committed || 'false' }}
pr-number:
description: 'The PR number from the changelog metadata'
value: ${{ steps.evaluate.outputs.pr-number }}

runs:
using: composite
steps:
- name: Download artifact
id: download
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: /tmp/changelog-result
run-id: ${{ inputs.run-id }}
github-token: ${{ inputs.github-token }}

- name: Evaluate artifact and validate PR
id: evaluate
if: steps.download.outcome == 'success'
uses: actions/github-script@v8
env:
COMMENT_ONLY: ${{ inputs.comment-only }}
with:
github-token: ${{ inputs.github-token }}
script: |
const script = require('${{ github.action_path }}/scripts/evaluate.js');
await script({ github, context, core });

- name: Checkout PR branch
if: steps.evaluate.outputs.should-commit == 'true'
uses: actions/checkout@v6
with:
ref: ${{ steps.evaluate.outputs.head-ref }}
token: ${{ inputs.github-token }}
persist-credentials: true

- name: Commit changelog
if: steps.evaluate.outputs.should-commit == 'true'
id: commit
shell: bash
env:
PR_NUMBER: ${{ steps.evaluate.outputs.pr-number }}
CHANGELOG_DIR: ${{ steps.evaluate.outputs.changelog-dir }}
run: |
CHANGELOG_FILE="$CHANGELOG_DIR/${PR_NUMBER}.yaml"

if [ -f "$CHANGELOG_FILE" ]; then
VERB="Update"
else
VERB="Add"
fi

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

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "$CHANGELOG_FILE"

if git diff --cached --quiet; then
echo "No changes to commit"
echo "committed=false" >> "$GITHUB_OUTPUT"
else
git commit -m "${VERB} changelog for PR #${PR_NUMBER}"
git push
echo "committed=true" >> "$GITHUB_OUTPUT"
fi

- name: Post success comment
if: steps.commit.outputs.committed == 'true'
uses: actions/github-script@v8
env:
PR_NUMBER: ${{ steps.evaluate.outputs.pr-number }}
HEAD_REF: ${{ steps.evaluate.outputs.head-ref }}
CHANGELOG_DIR: ${{ steps.evaluate.outputs.changelog-dir }}
with:
github-token: ${{ inputs.github-token }}
script: |
const script = require('${{ github.action_path }}/scripts/post-success-comment.js');
await script({ github, context, core });

- name: Post comment-only changelog
if: steps.evaluate.outputs.should-comment-success == 'true'
uses: actions/github-script@v8
env:
PR_NUMBER: ${{ steps.evaluate.outputs.pr-number }}
CHANGELOG_DIR: ${{ steps.evaluate.outputs.changelog-dir }}
with:
github-token: ${{ inputs.github-token }}
script: |
const script = require('${{ github.action_path }}/scripts/post-comment-only.js');
await script({ github, context, core });

- name: Post failure comment with available labels
if: steps.evaluate.outputs.should-comment-failure == 'true'
uses: actions/github-script@v8
env:
PR_NUMBER: ${{ steps.evaluate.outputs.pr-number }}
LABEL_TABLE: ${{ steps.evaluate.outputs.label-table }}
CONFIG_FILE: ${{ steps.evaluate.outputs.config-file }}
with:
github-token: ${{ inputs.github-token }}
script: |
const script = require('${{ github.action_path }}/scripts/post-failure-comment.js');
await script({ github, context, core });
Loading