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 -->
This commit is contained in:
myasnikovdaniil 2026-04-23 16:45:16 +05:00 committed by GitHub
commit 24d48bd075
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 32 deletions

View file

@ -255,6 +255,21 @@ jobs:
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
# Read-only token for the AI step. Minting a separate scoped token
# means the Generate changelog using AI step cannot push branches,
# open PRs, or mutate any repository even with --allow-all-tools,
# regardless of whether the agent follows the prompt's instructions.
- name: Generate read-only GitHub App token
id: app-token-read
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
permission-contents: read
permission-pull-requests: read
permission-metadata: read
- name: Parse tag
id: tag
uses: actions/github-script@v7
@ -303,50 +318,59 @@ jobs:
- name: Generate changelog using AI
if: steps.check_changelog.outputs.exists == 'false'
timeout-minutes: 30
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GH_TOKEN: ${{ steps.app-token-read.outputs.token }}
VERSION: ${{ steps.tag.outputs.version }}
run: |
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" \
copilot \
--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." \
--allow-all-tools --allow-all-paths < /dev/null
- name: Create changelog branch and commit
if: steps.check_changelog.outputs.exists == 'false'
env:
APP_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ steps.tag.outputs.version }}
run: |
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md"
CHANGELOG_BRANCH="changelog-v${{ steps.tag.outputs.version }}"
if [ -f "$CHANGELOG_FILE" ]; then
# Fetch latest main branch
git fetch origin main
# Delete local branch if it exists
git branch -D "$CHANGELOG_BRANCH" 2>/dev/null || true
# Create and checkout new branch from main
git checkout -b "$CHANGELOG_BRANCH" origin/main
# Add and commit changelog
git add "$CHANGELOG_FILE"
if git diff --staged --quiet; then
echo "⚠️ No changes to commit (file may already be committed)"
else
git commit -m "docs: add changelog for v${{ steps.tag.outputs.version }}" -s
echo "✅ Changelog committed to branch $CHANGELOG_BRANCH"
fi
# Push the branch (force push to update if it exists)
git push -f origin "$CHANGELOG_BRANCH"
else
echo "⚠️ Changelog file was not generated"
set -euo pipefail
CHANGELOG_FILE="docs/changelogs/v${VERSION}.md"
CHANGELOG_BRANCH="changelog-v${VERSION}"
if [ ! -f "$CHANGELOG_FILE" ]; then
echo "::error::Changelog file $CHANGELOG_FILE was not produced by the Generate changelog using AI step"
exit 1
fi
if [ ! -s "$CHANGELOG_FILE" ]; then
echo "::error::Changelog file $CHANGELOG_FILE is empty"
exit 1
fi
# Snapshot the file across the branch switch — the checkout below
# resets tracked files to match origin/main.
TEMP_FILE="$(mktemp)"
trap 'rm -f "$TEMP_FILE"' EXIT
cp "$CHANGELOG_FILE" "$TEMP_FILE"
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin "https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}"
git fetch origin main
git branch -D "$CHANGELOG_BRANCH" 2>/dev/null || true
git checkout -b "$CHANGELOG_BRANCH" origin/main
mkdir -p "$(dirname "$CHANGELOG_FILE")"
cp "$TEMP_FILE" "$CHANGELOG_FILE"
# The `check_changelog` step gated this job on the file being absent
# from origin/main, so `git add` + `git commit` must produce a diff.
# If they don't, something is wrong (e.g. empty file) — fail loud.
git add "$CHANGELOG_FILE"
git commit -m "docs: add changelog for v${VERSION}" -s
git push -f origin "$CHANGELOG_BRANCH"
- name: Create PR for changelog
if: steps.check_changelog.outputs.exists == 'false'

View file

@ -6,6 +6,20 @@ This file contains detailed instructions for AI-powered IDE on how to generate c
Follow these instructions when the user explicitly asks to generate a changelog.
## Scope and boundaries
**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.
Unless the caller explicitly instructs otherwise:
- **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).
- **Do not** push to any remote, open pull requests, or issue GitHub API write calls (POST / PATCH / DELETE) for any repository.
- 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.
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.
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.
## Required Tools
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:
**Save the changelog:**
Save the changelog to file `docs/changelogs/v<version>.md` according to the version for which the changelog is being generated.
**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.
### Important notes
- **After fetch with --force** local tags are up-to-date, use them for work