External Changelog Generation Research
Both phases implemented. This document is retained for historical context.
Issue: External fork PRs cannot push changelog files Related: ADR-005 - AI-Powered Changelog Generation Status: Shipped (Phase 1 and Phase 2) Date: 2025-01-18 Updated: 2026-01-26
Overview
The AI-powered changelog generation workflow handles PRs from external forks gracefully by using the pull_request_target trigger, which runs in the base repository context with full permissions to post comments.
Problem Statement
When using the pull_request trigger, PRs from external forks face permission restrictions:
- The
GITHUB_TOKENhas read-only access to the base repository - Write permissions declared in the workflow are ignored for fork PRs
- The workflow cannot push to the fork's branch (no write access to external repos)
- The workflow cannot even post comments on the PR (403 error)
Solution: Use pull_request_target trigger which runs in the base repository context with full permissions. This allows posting comments while maintaining security by never checking out fork code.
Options Considered
Option A: Generate Changelog at Merge Time
Move changelog generation from PR open to PR merge event (pull_request: closed with merged: true).
Pros:
- Works for all PRs (internal and external)
- No permission issues - always runs on main repo
- Simpler workflow (one trigger point)
Cons:
- Contributors can't preview/edit changelog before merge
- Triggers additional build after merge (creates noise)
- Adds commit to main after every merge
Decision: Rejected due to double-build issue.
Option B: Generate at Release Preparation Time
Defer changelog generation entirely to release time via a "Prepare Release" workflow.
Pros:
- No workflow changes during PR lifecycle
- Works for all PRs
- Can batch-generate and review before release
- Enables additional automation (release notes generation with AI)
Cons:
- Loses per-PR visibility of changelog entry
- Requires implementation of release automation workflow
Decision: Planned for Phase 2 implementation.
Option C: Fork Detection with Graceful Degradation (Selected)
Keep current workflow but handle forks gracefully:
- Detect if PR is from external fork
- For internal PRs: Push as usual
- For external PRs: Skip push, post comment with instructions
Pros:
- Minimal change to existing flow
- Internal PRs work exactly as before
- External contributors get AI-generated suggestion with clear instructions
- Quick to implement
Cons:
- External contributors need manual step (or maintainers add at merge)
- Two slightly different experiences
Decision: Selected for Phase 1 implementation.
Implementation Plan
Phase 1: Fork Detection with Graceful Degradation (✅ Implemented)
Status: Implemented with pull_request_target trigger
The workflow uses pull_request_target instead of pull_request to run in the base repository context, enabling:
- Full permissions to post comments on external fork PRs
- Access to repository secrets (GEMINI_API_KEY)
- Proper fork detection and handling
Security Considerations:
pull_request_targetruns with elevated permissions, so we must NEVER checkout and execute code from external forks- The workflow only uses PR metadata (title, body) for Gemini API calls, which is safe
- Changelog existence is checked via GitHub API, not by checking out code
- Checkout is only performed for internal PRs where
head.repo == base.repo
Behavior:
- Detect external forks by comparing
head.repo.full_namewithbase.repo.full_name - Check if changelog already exists via GitHub API (no checkout required)
- For external forks: Generate changelog from PR metadata, post instructional comment
- For internal PRs: Checkout, generate changelog, commit to PR branch, post confirmation comment
The comment for external forks includes:
- The AI-generated changelog content
- Clear instructions to create the file manually
- A copy-paste command for convenience
Phase 2: Release Automation (✅ Implemented)
Status: Implemented in .github/workflows/prepare-release.yml
Automated GitHub Actions workflow that:
- Validates changelog entries exist in
.changelog/directory - Runs release preparation script with specified version bump
- Creates release branch (
release/vX.Y.Z) - Commits all changes (package.json, package-lock.json, appdata.xml)
- Creates a pull request with detailed release notes
Triggers: Manual workflow dispatch from GitHub Actions UI
Benefits:
- No local setup required
- Consistent release process
- Automatic PR creation with standardized format
- Reduces human error in version bumping and file updates
Usage: See Release Process Documentation
Note: Phase 2 initially planned to fetch PRs and generate missing changelogs with Gemini, but this proved unnecessary since Phase 1 handles changelog generation at PR time. The workflow now focuses on automating the release preparation and PR creation steps.
Phase 3: Enhanced Release Notes (Future)
Extend the release automation to:
- Generate comprehensive release notes using Gemini
- Create GitHub release with formatted notes
- Update documentation automatically
Technical Details
Trigger Selection: pull_request_target vs pull_request
| Aspect | pull_request | pull_request_target |
|---|---|---|
| Runs in context of | Fork repository | Base repository |
| GITHUB_TOKEN permissions | Read-only for forks | Full (as declared) |
| Access to secrets | No (for forks) | Yes |
| Can post PR comments | No (for forks) | Yes |
| Security risk | Low | Higher (must not run fork code) |
Fork Detection Logic (via GitHub API)
// Safe - uses API metadata, not code checkout
const headRepo = context.payload.pull_request.head.repo.full_name;
const baseRepo = context.payload.pull_request.base.repo.full_name;
const isExternalFork = headRepo !== baseRepo;
// Check changelog existence via API (no checkout needed)
await github.rest.repos.getContent({
owner: context.payload.pull_request.head.repo.owner.login,
repo: context.payload.pull_request.head.repo.name,
path: `.changelog/pr-${prNumber}.txt`,
ref: context.payload.pull_request.head.sha
});
Comment Template for External Forks
📝 **Changelog entry generated:**
\`\`\`
{generated content}
\`\`\`
---
**To add this to your PR**, create `.changelog/pr-{prNumber}.txt`:
\`\`\`bash
cat <<'EOF' > .changelog/pr-{prNumber}.txt
{generated content}
EOF
git add .changelog/ && git commit -m "chore: add changelog entry" && git push
\`\`\`
Or create the file manually with the content above.
> **Note:** This step is required for external contributions. If not added, a maintainer will add it during the release process.