Changelog Drafter (dogfood) workflow (cobusgreyling/loop-engineering)
The Changelog Drafter (dogfood) workflow from cobusgreyling/loop-engineering, explained and optimized by Latchkey.
CI health: A - excellent
Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Changelog Drafter (dogfood) workflow from the cobusgreyling/loop-engineering repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Changelog Drafter (dogfood)
on:
schedule:
- cron: '30 18 * * 1'
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
draft:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Summarize release window
id: window
run: |
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
SINCE=$(git log -1 --format=%ci "${LAST_TAG}" 2>/dev/null || git log -1 --format=%ci)
echo "last_tag=${LAST_TAG}" >> "$GITHUB_OUTPUT"
echo "since=${SINCE}" >> "$GITHUB_OUTPUT"
git log --oneline "${LAST_TAG}..HEAD" 2>/dev/null | head -30 || git log --oneline -15
- name: Open or update release-prep issue (L1 - human drafts notes)
uses: actions/github-script@v9
with:
script: |
const title = `Release prep - week of ${new Date().toISOString().slice(0, 10)}`;
const body = [
'## Changelog drafter (L1 dogfood)',
'',
'Automated scan surface for release notes. **Human reviews and edits before any publish.**',
'',
`- Last tag: \`${{ steps.window.outputs.last_tag }}\``,
`- Window since: ${{ steps.window.outputs.since }}`,
'',
'### Next steps',
'1. Run the changelog-drafter starter skills locally or in your agent TUI.',
'2. Produce `RELEASE_NOTES_DRAFT.md` from merges since the last tag.',
'3. Update `changelog-drafter-state.md` when the draft is ready.',
'',
'Pattern: [changelog-drafter.md](https://github.com/cobusgreyling/loop-engineering/blob/main/patterns/changelog-drafter.md)',
'',
'_Generated by `.github/workflows/changelog-drafter.yml` - report-only, no auto-publish._',
].join('\n');
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'release-prep',
state: 'open',
per_page: 1,
});
if (issues.length) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issues[0].number,
body: `### Weekly scan refresh\n\n${body}`,
});
return;
}
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'release-prep',
color: '3ee8c5',
description: 'Release notes draft tracking (changelog-drafter L1)',
});
} catch (_) {}
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
labels: ['release-prep'],
body,
});The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Changelog Drafter (dogfood) on: schedule: - cron: '30 18 * * 1' workflow_dispatch: permissions: contents: read issues: write jobs: draft: timeout-minutes: 30 runs-on: latchkey-small steps: - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Summarize release window id: window run: | LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "none") SINCE=$(git log -1 --format=%ci "${LAST_TAG}" 2>/dev/null || git log -1 --format=%ci) echo "last_tag=${LAST_TAG}" >> "$GITHUB_OUTPUT" echo "since=${SINCE}" >> "$GITHUB_OUTPUT" git log --oneline "${LAST_TAG}..HEAD" 2>/dev/null | head -30 || git log --oneline -15 - name: Open or update release-prep issue (L1 - human drafts notes) uses: actions/github-script@v9 with: script: | const title = `Release prep - week of ${new Date().toISOString().slice(0, 10)}`; const body = [ '## Changelog drafter (L1 dogfood)', '', 'Automated scan surface for release notes. **Human reviews and edits before any publish.**', '', `- Last tag: \`${{ steps.window.outputs.last_tag }}\``, `- Window since: ${{ steps.window.outputs.since }}`, '', '### Next steps', '1. Run the changelog-drafter starter skills locally or in your agent TUI.', '2. Produce `RELEASE_NOTES_DRAFT.md` from merges since the last tag.', '3. Update `changelog-drafter-state.md` when the draft is ready.', '', 'Pattern: [changelog-drafter.md](https://github.com/cobusgreyling/loop-engineering/blob/main/patterns/changelog-drafter.md)', '', '_Generated by `.github/workflows/changelog-drafter.yml` - report-only, no auto-publish._', ].join('\n'); const { data: issues } = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, labels: 'release-prep', state: 'open', per_page: 1, }); if (issues.length) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issues[0].number, body: `### Weekly scan refresh\n\n${body}`, }); return; } try { await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: 'release-prep', color: '3ee8c5', description: 'Release notes draft tracking (changelog-drafter L1)', }); } catch (_) {} await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title, labels: ['release-prep'], body, });
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.