Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 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
23 changes: 23 additions & 0 deletions .github/workflows/changelog-generate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Changelog generate

on:
workflow_call:
inputs:
config:
description: 'Path to changelog.yml configuration file'
type: string
default: 'docs/changelog.yml'

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 }}
25 changes: 25 additions & 0 deletions .github/workflows/changelog-submit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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
Comment thread
cotti marked this conversation as resolved.
steps:
- name: Submit changelog
uses: elastic/docs-actions/changelog/submit@v1
with:
comment-only: ${{ inputs.comment-only }}
152 changes: 152 additions & 0 deletions changelog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Changelog automation

Automatically generate and submit 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 workflows

Add two workflow files to your repository:

**`.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-submit.yml`**

```yaml
name: changelog-submit

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.
submit:
uses: elastic/docs-actions/.github/workflows/changelog-submit.yml@v1
```

> **Important:** The `name` in the generate workflow (`changelog-generate`) must match the `workflows:` reference in the submit 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 submit 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 handling PR branches, including those from forks.

### 3. Create the labels

Make sure the GitHub labels referenced in your `docs/changelog.yml` exist in your repository. You can control which PRs generate changelog entries using `rules.create` in your config -- either by excluding PRs with certain labels or by requiring specific labels to be present. For details, see [Rules for creation and publishing](https://elastic.github.io/docs-builder/contribute/changelog/#rules-for-creation-and-publishing).

## How it works

```
PR opened/labeled/title edited
|
v
generate workflow (read-only)
|
+-- skip if labels match rules.create exclusion rules
+-- skip if last commit is from the bot (prevents loops)
+-- skip if changelog file was manually edited
+-- skip if only the PR body was edited (not the title)
|
+-- resolves title and type from PR metadata + config
+-- runs: docs-builder changelog add
+-- uploads result as artifact
|
v
submit workflow (write permissions, via workflow_run)
|
+-- downloads artifact
+-- re-validates PR state (labels, head SHA, fork detection)
+-- commits changelog file to PR branch
+-- posts PR comment with view/edit links
|
+-- fork PRs: posts changelog as comment instead
+-- no-label PRs: posts comment listing available labels
```

### Comment-only mode

If you prefer not to have bot commits on your PR branches, pass `comment-only: true` to the submit workflow. The changelog content will be posted as a PR comment instead:

```yaml
jobs:
submit:
uses: elastic/docs-actions/.github/workflows/changelog-submit.yml@v1
with:
comment-only: true
```

Fork PRs automatically use comment-only mode since the workflow token cannot push to fork branches.

## Skipping changelog generation

Configure `rules.create` in your `docs/changelog.yml` to control which PRs generate changelog entries. For example, to skip PRs with a `changelog:skip` label:

```yaml
rules:
create:
exclude: "changelog:skip"
```

When all products are blocked by the create rules, the generate action will exit early and no artifact or commit is produced. You can also use `include` mode or per-product overrides. See [Rules for creation and publishing](https://elastic.github.io/docs-builder/contribute/changelog/#rules-for-creation-and-publishing) for the full 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
143 changes: 143 additions & 0 deletions changelog/generate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: Changelog generate
description: >
Generate a changelog entry for a pull request using docs-builder.
Handles setup, changelog generation, artifact upload, metadata creation and PR guard checks.

inputs:
config:
description: 'Path to changelog.yml configuration file'
default: 'docs/changelog.yml'
strip-title-prefix:
description: 'Remove [Prefix]: from PR titles'
default: 'true'
github-token:
description: 'GitHub token for API access'
default: '${{ github.token }}'

outputs:
status:
description: 'Result: success, no-label, no-title, error, manually-edited, or skipped'
value: ${{ steps.resolve.outputs.status }}

runs:
using: composite
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup docs-builder
uses: elastic/docs-actions/docs-builder/setup@v1
with:
version: latest
github-token: ${{ inputs.github-token }}

- name: Evaluate PR
id: evaluate
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
CONFIG_FILE: ${{ inputs.config }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
EVENT_ACTION: ${{ github.event.action }}
TITLE_CHANGED: ${{ github.event.changes.title != null }}
STRIP_PREFIX: ${{ inputs.strip-title-prefix }}
run: |
Comment thread
cotti marked this conversation as resolved.
Outdated
TITLE_FLAG=""
if [ "$TITLE_CHANGED" = "true" ]; then
TITLE_FLAG="--title-changed"
fi

PREFIX_FLAG=""
if [ "$STRIP_PREFIX" = "true" ]; then
PREFIX_FLAG="--strip-title-prefix"
fi

docs-builder changelog evaluate-pr \
--config "$CONFIG_FILE" \
--owner "${{ github.repository_owner }}" \
--repo "${{ github.event.repository.name }}" \
--pr-number "$PR_NUMBER" \
--pr-title "$PR_TITLE" \
--pr-labels "$PR_LABELS" \
--head-ref "$HEAD_REF" \
--head-sha "$HEAD_SHA" \
--event-action "$EVENT_ACTION" \
$TITLE_FLAG \
$PREFIX_FLAG

- name: Run changelog add
id: generate
if: steps.evaluate.outputs.should-generate == 'true'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
CONFIG_FILE: ${{ inputs.config }}
run: |
mkdir -p /tmp/changelog-staging
docs-builder changelog add \
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.

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

--concise \
--config "$CONFIG_FILE" \
--output /tmp/changelog-staging

- name: Prepare artifact
if: always() && steps.evaluate.outputs.should-upload == 'true'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
CONFIG_FILE: ${{ inputs.config }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
EVALUATE_STATUS: ${{ steps.evaluate.outputs.status }}
GENERATE_OUTCOME: ${{ steps.generate.outcome }}
LABEL_TABLE: ${{ steps.evaluate.outputs.label-table }}
run: |
docs-builder changelog prepare-artifact \
--staging-dir /tmp/changelog-staging \
--output-dir /tmp/changelog-result \
--evaluate-status "$EVALUATE_STATUS" \
--generate-outcome "${GENERATE_OUTCOME:-skipped}" \
--pr-number "$PR_NUMBER" \
--head-ref "$HEAD_REF" \
--head-sha "$HEAD_SHA" \
--config "$CONFIG_FILE" \
${LABEL_TABLE:+--label-table "$LABEL_TABLE"}

- name: Upload artifact
if: always() && steps.evaluate.outputs.should-upload == 'true'
uses: actions/upload-artifact@v4
with:
name: changelog-result
path: /tmp/changelog-result/
retention-days: 1

- name: Resolve generation outcome
id: resolve
if: always()
shell: bash
run: |
STATUS="${{ steps.evaluate.outputs.status }}"
if [ "$STATUS" = "proceed" ]; then
if [ "${{ steps.generate.outcome }}" = "success" ]; then
STATUS="success"
else
STATUS="error"
fi
fi
STATUS="${STATUS:-skipped}"
echo "status=$STATUS" >> "$GITHUB_OUTPUT"

case "$STATUS" in
no-title)
echo "::error::PR has no title. Cannot generate a changelog entry without a title."
exit 1
;;
error)
echo "::error::Changelog generation failed unexpectedly."
exit 1
;;
esac
Loading