Skip to content

Commit 24d48bd

Browse files
fix(ci): harden changelog generation in tags.yaml (v1.3.0 regression) (#2460)
## What this PR does Fixes the failures that blocked the v1.3.0 release pipeline (workflow run [24765377017]) and closes the gap in `docs/agents/changelog.md` that made them possible. ### Bug 1 — pathspec error in `Create changelog branch and commit` After the Copilot step, the workflow runs `git checkout -b "$CHANGELOG_BRANCH" origin/main`. Copilot CLI was invoked with `--allow-all-tools` and had committed the generated changelog onto HEAD of `main`, so the reset to `origin/main` deleted the tracked file, and `git add "$CHANGELOG_FILE"` then failed with `fatal: pathspec ... did not match any files`. Fix (commit 2): - Snapshot the file across the branch switch so the checkout cannot drop it. - `set -euo pipefail` so any failure surfaces loudly. - Drop the dead "no changes to commit" soft-branch — the earlier `check_changelog` step gates this step on the file being absent from `origin/main`, so `git commit` must produce a diff; if it doesn't (e.g. empty file), fail loud instead of pushing an empty branch. - Fail-early file-existence check with `::error::` annotation, and `VERSION` moved to step `env:`. ### Bug 2 — no timeout on `Generate changelog using AI` The re-run hung in Copilot for 10+ minutes with zero log output. With no `timeout-minutes`, a hung Copilot would hold a self-hosted runner for 6 hours (job default). Fix (commit 2): `timeout-minutes: 30` (the prior successful run took ~26 minutes). ### Root-cause fix — scope the agent prompt `docs/agents/changelog.md` is the actual prompt driving Copilot. It ended with "Save the changelog" and gave no boundary, so an agent with `--allow-all-tools` could reasonably interpret "done" as "commit, push, open a PR". Fix (commit 1): - Add a "Scope and boundaries" section at the top stating the single deliverable is `docs/changelogs/v<version>.md` and enumerating forbidden operations (git commit / push / branch / tag / reset / merge / rebase, PR creation, GitHub API writes, modifying any file other than the changelog). - Add an explicit "then exit" at the end of Step 9 with a back-reference to the scope section. - Scoped "unless the caller explicitly instructs otherwise" so interactive IDE use stays flexible. With the rules in the doc, CI and interactive callers share the same boundary, and the workflow prompt becomes a one-line invocation (`--prompt "Generate the release changelog for tag v${VERSION}. Follow the instructions in @docs/agents/changelog.md exactly, including the 'Scope and boundaries' section at the top. ..."`). ### Files touched - `docs/agents/changelog.md` — +16 / -0 (scope section + exit rule) - `.github/workflows/tags.yaml` — +36 / -31 (timeout + rewritten commit step + simplified prompt) Other jobs in the workflow (`prepare-release`, `update-website-docs`) are untouched. ### Release note ```release-note NONE ``` [24765377017]: https://github.com/cozystack/cozystack/actions/runs/24765377017 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Improved release automation reliability with stricter verification, enforced error handling, and a timeout for changelog generation * Ensured automated commits/pushes occur only after successful output validation to prevent accidental repository mutations * **Documentation** * Clarified agent instructions to produce a single changelog file and terminate, and to restrict the agent to read-only inspection during generation <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents a92dc76 + 76c4eab commit 24d48bd

2 files changed

Lines changed: 72 additions & 32 deletions

File tree

‎.github/workflows/tags.yaml‎

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,21 @@ jobs:
255255
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
256256
owner: cozystack
257257

258+
# Read-only token for the AI step. Minting a separate scoped token
259+
# means the Generate changelog using AI step cannot push branches,
260+
# open PRs, or mutate any repository even with --allow-all-tools,
261+
# regardless of whether the agent follows the prompt's instructions.
262+
- name: Generate read-only GitHub App token
263+
id: app-token-read
264+
uses: actions/create-github-app-token@v1
265+
with:
266+
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
267+
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
268+
owner: cozystack
269+
permission-contents: read
270+
permission-pull-requests: read
271+
permission-metadata: read
272+
258273
- name: Parse tag
259274
id: tag
260275
uses: actions/github-script@v7
@@ -303,51 +318,60 @@ jobs:
303318

304319
- name: Generate changelog using AI
305320
if: steps.check_changelog.outputs.exists == 'false'
321+
timeout-minutes: 30
306322
env:
307323
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
308-
GH_TOKEN: ${{ steps.app-token.outputs.token }}
324+
GH_TOKEN: ${{ steps.app-token-read.outputs.token }}
325+
VERSION: ${{ steps.tag.outputs.version }}
309326
run: |
310-
copilot --prompt "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" \
327+
copilot \
328+
--prompt "Generate the release changelog for tag v${VERSION}. Follow the instructions in @docs/agents/changelog.md exactly, including the 'Scope and boundaries' section at the top. Your deliverable is the single file docs/changelogs/v${VERSION}.md — write it and exit; this workflow handles branching, committing, pushing, and opening the PR." \
311329
--allow-all-tools --allow-all-paths < /dev/null
312330
313331
- name: Create changelog branch and commit
314332
if: steps.check_changelog.outputs.exists == 'false'
315333
env:
316334
APP_TOKEN: ${{ steps.app-token.outputs.token }}
335+
VERSION: ${{ steps.tag.outputs.version }}
317336
run: |
318-
git config user.name "cozystack-ci[bot]"
319-
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
320-
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
321-
322-
CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md"
323-
CHANGELOG_BRANCH="changelog-v${{ steps.tag.outputs.version }}"
324-
325-
if [ -f "$CHANGELOG_FILE" ]; then
326-
# Fetch latest main branch
327-
git fetch origin main
328-
329-
# Delete local branch if it exists
330-
git branch -D "$CHANGELOG_BRANCH" 2>/dev/null || true
331-
332-
# Create and checkout new branch from main
333-
git checkout -b "$CHANGELOG_BRANCH" origin/main
334-
335-
# Add and commit changelog
336-
git add "$CHANGELOG_FILE"
337-
if git diff --staged --quiet; then
338-
echo "⚠️ No changes to commit (file may already be committed)"
339-
else
340-
git commit -m "docs: add changelog for v${{ steps.tag.outputs.version }}" -s
341-
echo "✅ Changelog committed to branch $CHANGELOG_BRANCH"
342-
fi
343-
344-
# Push the branch (force push to update if it exists)
345-
git push -f origin "$CHANGELOG_BRANCH"
346-
else
347-
echo "⚠️ Changelog file was not generated"
337+
set -euo pipefail
338+
339+
CHANGELOG_FILE="docs/changelogs/v${VERSION}.md"
340+
CHANGELOG_BRANCH="changelog-v${VERSION}"
341+
342+
if [ ! -f "$CHANGELOG_FILE" ]; then
343+
echo "::error::Changelog file $CHANGELOG_FILE was not produced by the Generate changelog using AI step"
344+
exit 1
345+
fi
346+
if [ ! -s "$CHANGELOG_FILE" ]; then
347+
echo "::error::Changelog file $CHANGELOG_FILE is empty"
348348
exit 1
349349
fi
350350
351+
# Snapshot the file across the branch switch — the checkout below
352+
# resets tracked files to match origin/main.
353+
TEMP_FILE="$(mktemp)"
354+
trap 'rm -f "$TEMP_FILE"' EXIT
355+
cp "$CHANGELOG_FILE" "$TEMP_FILE"
356+
357+
git config user.name "cozystack-ci[bot]"
358+
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
359+
git remote set-url origin "https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}"
360+
361+
git fetch origin main
362+
git branch -D "$CHANGELOG_BRANCH" 2>/dev/null || true
363+
git checkout -b "$CHANGELOG_BRANCH" origin/main
364+
365+
mkdir -p "$(dirname "$CHANGELOG_FILE")"
366+
cp "$TEMP_FILE" "$CHANGELOG_FILE"
367+
368+
# The `check_changelog` step gated this job on the file being absent
369+
# from origin/main, so `git add` + `git commit` must produce a diff.
370+
# If they don't, something is wrong (e.g. empty file) — fail loud.
371+
git add "$CHANGELOG_FILE"
372+
git commit -m "docs: add changelog for v${VERSION}" -s
373+
git push -f origin "$CHANGELOG_BRANCH"
374+
351375
- name: Create PR for changelog
352376
if: steps.check_changelog.outputs.exists == 'false'
353377
uses: actions/github-script@v7

‎docs/agents/changelog.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ This file contains detailed instructions for AI-powered IDE on how to generate c
66

77
Follow these instructions when the user explicitly asks to generate a changelog.
88

9+
## Scope and boundaries
10+
11+
**Your single deliverable is the file `docs/changelogs/v<version>.md`.** Write the complete, verified changelog to that path. That is the entire task. Exit as soon as the file is written and verified against the checklist in Step 9.
12+
13+
Unless the caller explicitly instructs otherwise:
14+
15+
- **In the cozystack working tree**, do not run `git commit`, `git push`, `git checkout` (to switch branches), `git branch`, `git tag`, `git reset`, `git merge`, or `git rebase`. Do not write to local branches, tags, or HEAD. `git fetch` is expected and fine (see the read-only analysis list below).
16+
- **Do not** push to any remote, open pull requests, or issue GitHub API write calls (POST / PATCH / DELETE) for any repository.
17+
- In the cozystack working tree, the **only** file you create or modify is `docs/changelogs/v<version>.md`. Cloning auxiliary repositories under `_repos/` for cross-repo analysis (see Step 6) is fine; local git operations inside those disposable clones (`git checkout`, `git pull`, etc.) are allowed — just never push from them or open PRs against them.
18+
19+
The caller — a GitHub Actions workflow in CI, or a developer running you interactively — owns branching, committing, pushing, and PR creation. They will perform those actions after you exit. Do not pre-empt them even if the working tree looks ready.
20+
21+
Read-only analysis is expected and encouraged: `git log`, `git show`, `git fetch`, `git diff`, `gh pr view`, `gh api` GET requests, and reading any file in the repository.
22+
923
## Required Tools
1024

1125
Before generating changelogs, ensure you have access to `gh` (GitHub CLI) tool, which is used to fetch commit and PR author information. The GitHub CLI is used to correctly identify PR authors from commits and pull requests.
@@ -608,6 +622,8 @@ Create a new changelog file in the format matching previous versions:
608622
**Save the changelog:**
609623
Save the changelog to file `docs/changelogs/v<version>.md` according to the version for which the changelog is being generated.
610624
625+
**Then exit.** Do not commit, push, create a branch, or open a pull request — the caller handles all git and GitHub operations after you return. See the "Scope and boundaries" section at the top of this document.
626+
611627
### Important notes
612628
613629
- **After fetch with --force** local tags are up-to-date, use them for work

0 commit comments

Comments
 (0)