Skip to content
Latchkey

Changelog Drafter (dogfood) workflow (cobusgreyling/loop-engineering)

The Changelog Drafter (dogfood) workflow from cobusgreyling/loop-engineering, explained and optimized by Latchkey.

A

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.

Grade your own workflow free or run it on Latchkey →
Source: cobusgreyling/loop-engineering.github/workflows/changelog-drafter.ymlLicense MITView source

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

workflow (.yml)
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

This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow