934 lines
29 KiB
Markdown
934 lines
29 KiB
Markdown
# Auto-Versioning CI Failure Remediation Plan
|
||
|
||
**Date:** January 15, 2026
|
||
**Issue:** Repository rule violations preventing tag creation in CI
|
||
**Error:** `GH013: Repository rule violations found for refs/tags/v1.0.0 - Cannot create ref due to creations being restricted`
|
||
**Affected Workflow:** `.github/workflows/auto-versioning.yml`
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
The auto-versioning workflow fails during tag creation because GitHub repository rules are blocking the `GITHUB_TOKEN` from creating tags. This is a common security configuration that prevents automated tag creation without proper permissions.
|
||
|
||
**Root Cause:** GitHub repository rules (tag protection) prevent the default `GITHUB_TOKEN` from creating tags matching protected patterns (e.g., `v*`).
|
||
|
||
**Recommended Solution:** Use GitHub's release creation API instead of `git push` to create tags, as releases with tags are allowed through repository rules with appropriate workflow permissions.
|
||
|
||
**Alternative Solutions:** (1) Adjust repository rules to allow workflow tag creation, (2) Use a fine-grained PAT with tag creation permissions, or (3) Use the GitHub API to create tags directly.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Root Cause Analysis](#root-cause-analysis)
|
||
2. [Current Workflow Analysis](#current-workflow-analysis)
|
||
3. [Recommended Solution](#recommended-solution)
|
||
4. [Alternative Approaches](#alternative-approaches)
|
||
5. [Implementation Guide](#implementation-guide)
|
||
6. [Security Considerations](#security-considerations)
|
||
7. [Testing & Validation](#testing--validation)
|
||
8. [Rollback Plan](#rollback-plan)
|
||
|
||
---
|
||
|
||
## Root Cause Analysis
|
||
|
||
### GitHub Repository Rules Overview
|
||
|
||
GitHub repository rules are a security feature that allows repository administrators to enforce protection on branches, tags, and other refs. These rules can:
|
||
|
||
- Prevent force pushes
|
||
- Require status checks before merging
|
||
- Restrict who can create or delete tags
|
||
- Require signed commits
|
||
- Prevent creation of tags matching specific patterns
|
||
|
||
### Why the Workflow Failed
|
||
|
||
The error message indicates:
|
||
|
||
```
|
||
remote: error: GH013: Repository rule violations found for refs/tags/v1.0.0.
|
||
remote: - Cannot create ref due to creations being restricted.
|
||
remote: ! [remote rejected] v1.0.0 -> v1.0.0 (push declined due to repository rule violations)
|
||
```
|
||
|
||
**Analysis:**
|
||
|
||
1. **Repository Rule Active:** The repository has a rule restricting tag creation for patterns like `v*` (version tags)
|
||
2. **Token Insufficient:** The default `GITHUB_TOKEN` used in the workflow lacks permissions to bypass these rules
|
||
3. **Git Push Blocked:** The workflow uses `git push origin "${TAG}"` which is subject to repository rules
|
||
4. **No Bypass Mechanism:** The workflow has no mechanism to bypass or work around the protection
|
||
|
||
### Current Workflow Design
|
||
|
||
File: `.github/workflows/auto-versioning.yml`
|
||
|
||
The workflow:
|
||
1. ✅ Calculates semantic version using conventional commits
|
||
2. ✅ Creates an annotated git tag locally
|
||
3. ❌ **FAILS HERE:** Attempts to `git push origin "${TAG}"` using `GITHUB_TOKEN`
|
||
4. ⚠️ Falls back to creating GitHub Release with existing (non-existent) tag
|
||
|
||
**Key problematic code (lines 65-70):**
|
||
|
||
```yaml
|
||
git tag -a "${TAG}" -m "Release ${TAG}"
|
||
git push origin "${TAG}"
|
||
```
|
||
|
||
This direct push approach fails because:
|
||
- `GITHUB_TOKEN` has `contents: write` permission
|
||
- But repository rules override this permission for protected refs
|
||
- The token cannot bypass repository rules by default
|
||
|
||
---
|
||
|
||
## Current Workflow Analysis
|
||
|
||
### Workflow File: `.github/workflows/auto-versioning.yml`
|
||
|
||
**Trigger:** Push to `main` branch
|
||
|
||
**Permissions:**
|
||
```yaml
|
||
permissions:
|
||
contents: write # ← Has write permission but blocked by repo rules
|
||
pull-requests: write
|
||
```
|
||
|
||
**Critical Steps:**
|
||
|
||
| Step | Description | Status | Issue |
|
||
|------|-------------|--------|-------|
|
||
| `Calculate Semantic Version` | Uses `paulhatch/semantic-version@v5.4.0` | ✅ Working | None |
|
||
| `Show version` | Displays calculated version | ✅ Working | None |
|
||
| `Create annotated tag and push` | **Creates tag and pushes via git** | ❌ **FAILING** | **Repository rules block git push** |
|
||
| `Determine tag` | Extracts tag for release | ⚠️ Conditional | Depends on failed step |
|
||
| `Check for existing GitHub Release` | Checks if release exists | ✅ Working | None |
|
||
| `Create GitHub Release` | Creates release if not exists | ⚠️ Conditional | Tag doesn't exist yet |
|
||
|
||
**Problem Flow:**
|
||
|
||
```
|
||
┌─────────────────────────────────┐
|
||
│ 1. Calculate version: v1.0.0 │
|
||
└──────────┬──────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────┐
|
||
│ 2. Create tag locally │
|
||
│ git tag -a v1.0.0 │
|
||
└──────────┬──────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────┐
|
||
│ 3. Push tag to remote │
|
||
│ git push origin v1.0.0 │ ❌ BLOCKED BY REPOSITORY RULES
|
||
└──────────┬──────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────┐
|
||
│ 4. Create GitHub Release │
|
||
│ with tag_name: v1.0.0 │ ⚠️ Tag doesn't exist on remote
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
### Related Files
|
||
|
||
**Reviewed configuration files:**
|
||
|
||
- `.gitignore`: ✅ No tag-related exclusions (appropriate)
|
||
- `.dockerignore`: ✅ No impact on workflow (appropriate)
|
||
- `Dockerfile`: ✅ No impact on versioning workflow (appropriate)
|
||
- `codecov.yml`: ❌ File not found (not relevant to this issue)
|
||
|
||
---
|
||
|
||
## Recommended Solution
|
||
|
||
### Approach: Use GitHub Release API Instead of Git Push
|
||
|
||
**Rationale:**
|
||
|
||
1. **Bypasses Repository Rules:** GitHub Releases API is designed to work with repository rules
|
||
2. **Atomic Operation:** Creates tag and release in one API call
|
||
3. **No Extra Permissions:** Uses existing `GITHUB_TOKEN` with `contents: write`
|
||
4. **Industry Standard:** Widely used pattern in GitHub Actions workflows
|
||
5. **Better UX:** Release notes generated automatically via `generate_release_notes: true`
|
||
|
||
**How It Works:**
|
||
|
||
The `softprops/action-gh-release` action (already in the workflow) can create tags as part of the release creation process:
|
||
|
||
```yaml
|
||
- name: Create GitHub Release (creates tag automatically)
|
||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||
with:
|
||
tag_name: ${{ steps.determine_tag.outputs.tag }}
|
||
name: Release ${{ steps.determine_tag.outputs.tag }}
|
||
generate_release_notes: true
|
||
make_latest: true # ← Changed from false to mark as latest release
|
||
```
|
||
|
||
**Key Differences from Current Approach:**
|
||
|
||
| Aspect | Current (git push) | Recommended (API) |
|
||
|--------|-------------------|-------------------|
|
||
| **Tag Creation** | Local + remote push | Remote via API |
|
||
| **Repository Rules** | Blocked | Allowed |
|
||
| **Permissions Required** | `contents: write` + bypass rules | `contents: write` only |
|
||
| **Failure Mode** | Tag push fails, release creation may partially succeed | Atomic operation |
|
||
| **Rollback** | Manual tag deletion | Delete release (deletes tag) |
|
||
|
||
### Implementation Strategy
|
||
|
||
**Phase 1: Remove Git Push** (Lines 65-70)
|
||
- Remove the `git tag` and `git push` commands
|
||
- Remove the local tag creation step entirely
|
||
|
||
**Phase 2: Simplify Logic** (Lines 50-90)
|
||
- Remove duplicate tag existence checks
|
||
- Remove conditional logic around tag creation
|
||
- Rely solely on GitHub Release API
|
||
|
||
**Phase 3: Update Release Creation** (Lines 95-105)
|
||
- Change `make_latest: false` to `make_latest: true` for proper release tagging
|
||
- Ensure tag creation happens via API
|
||
|
||
**Phase 4: Simplify Conditionals**
|
||
- Remove `steps.semver.outputs.changed` conditions (always create if doesn't exist)
|
||
- Keep only `steps.check_release.outputs.exists == 'false'` condition
|
||
|
||
---
|
||
|
||
## Alternative Approaches
|
||
|
||
### Alternative 1: Adjust Repository Rules (Not Recommended)
|
||
|
||
**Approach:** Modify repository rules to allow GitHub Actions to bypass tag protection.
|
||
|
||
**Pros:**
|
||
- ✅ Minimal code changes
|
||
- ✅ Keeps current git-based workflow
|
||
|
||
**Cons:**
|
||
- ❌ Reduces security posture
|
||
- ❌ Requires repository admin access
|
||
- ❌ May not be possible in organizations with strict policies
|
||
- ❌ Not portable across repositories
|
||
- ❌ Conflicts with best practices for protected tags
|
||
|
||
**Implementation:**
|
||
1. Go to Repository Settings → Rules → Rulesets
|
||
2. Find the ruleset protecting `v*` tags
|
||
3. Add bypass for GitHub Actions
|
||
4. Risk: Opens security hole for all workflows
|
||
|
||
**Recommendation:** ❌ **Do not use** unless organizational policy requires git-based tagging
|
||
|
||
---
|
||
|
||
### Alternative 2: Fine-Grained Personal Access Token (Discouraged)
|
||
|
||
**Approach:** Create a fine-grained PAT with tag creation permissions and store in GitHub Secrets.
|
||
|
||
**Pros:**
|
||
- ✅ Can bypass repository rules
|
||
- ✅ Fine-grained permission scope
|
||
- ✅ Keeps current git-based workflow
|
||
|
||
**Cons:**
|
||
- ❌ Requires manual token creation and rotation
|
||
- ❌ Token expiration management overhead
|
||
- ❌ Security risk if token leaks
|
||
- ❌ Not recommended by GitHub for automated workflows
|
||
- ❌ Requires storing secrets
|
||
- ❌ Breaks on token expiration without warning
|
||
|
||
**Implementation:**
|
||
1. Create fine-grained PAT with:
|
||
- Repository access: This repository only
|
||
- Permissions: Contents (write), Metadata (read)
|
||
- Expiration: 90 days (max for fine-grained)
|
||
2. Store as secret: `AUTO_VERSION_PAT`
|
||
3. Update workflow:
|
||
```yaml
|
||
- uses: actions/checkout@v6
|
||
with:
|
||
token: ${{ secrets.AUTO_VERSION_PAT }}
|
||
```
|
||
4. Set up token rotation reminder
|
||
|
||
**Recommendation:** ⚠️ **Use only if** API approach doesn't meet requirements
|
||
|
||
---
|
||
|
||
### Alternative 3: Direct GitHub API Tag Creation (Complex)
|
||
|
||
**Approach:** Use GitHub API to create tag objects directly, bypassing git push.
|
||
|
||
**Pros:**
|
||
- ✅ Can work with repository rules
|
||
- ✅ No secrets required beyond `GITHUB_TOKEN`
|
||
- ✅ Programmatic control
|
||
|
||
**Cons:**
|
||
- ❌ More complex implementation
|
||
- ❌ Requires creating git objects via API (ref, commit, tag)
|
||
- ❌ Error handling complexity
|
||
- ❌ Less maintainable than standard release workflow
|
||
|
||
**Implementation Sketch:**
|
||
```yaml
|
||
- name: Create tag via API
|
||
uses: actions/github-script@v7
|
||
with:
|
||
script: |
|
||
const tag = '${{ steps.semver.outputs.version }}';
|
||
const sha = context.sha;
|
||
|
||
// Create tag reference
|
||
await github.rest.git.createRef({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
ref: `refs/tags/${tag}`,
|
||
sha: sha
|
||
});
|
||
```
|
||
|
||
**Recommendation:** ⚠️ **Use only if** release-based approach has limitations
|
||
|
||
---
|
||
|
||
### Alternative 4: Use `release-please` or Similar Tools
|
||
|
||
**Approach:** Replace custom workflow with industry-standard release automation.
|
||
|
||
**Tools:**
|
||
- `googleapis/release-please-action` (Google's release automation)
|
||
- `semantic-release/semantic-release` (npm ecosystem)
|
||
- `go-semantic-release/semantic-release` (Go ecosystem)
|
||
|
||
**Pros:**
|
||
- ✅ Battle-tested industry standard
|
||
- ✅ Handles repository rules correctly
|
||
- ✅ Better changelog generation
|
||
- ✅ Supports multiple languages
|
||
- ✅ Automatic versioning in files
|
||
|
||
**Cons:**
|
||
- ❌ Requires more significant refactoring
|
||
- ❌ May change current workflow behavior
|
||
- ❌ Learning curve for team
|
||
|
||
**Recommendation:** 💡 **Consider for future enhancement** but not for immediate fix
|
||
|
||
---
|
||
|
||
## Implementation Guide
|
||
|
||
### Step-by-Step Implementation (Recommended Solution)
|
||
|
||
#### Step 1: Backup Current Workflow
|
||
|
||
```bash
|
||
cp .github/workflows/auto-versioning.yml .github/workflows/auto-versioning.yml.backup
|
||
```
|
||
|
||
#### Step 2: Update Workflow File
|
||
|
||
**File:** `.github/workflows/auto-versioning.yml`
|
||
|
||
**Change 1: Remove Local Tag Creation and Push** (Lines 50-80)
|
||
|
||
Replace:
|
||
```yaml
|
||
- id: create_tag
|
||
name: Create annotated tag and push
|
||
if: ${{ steps.semver.outputs.changed }}
|
||
run: |
|
||
# Ensure a committer identity is configured in the runner so git tag works
|
||
git config --global user.email "actions@github.com"
|
||
git config --global user.name "GitHub Actions"
|
||
|
||
# Normalize the version: remove any leading 'v' so we don't end up with 'vvX.Y.Z'
|
||
RAW="${{ steps.semver.outputs.version }}"
|
||
VERSION_NO_V="${RAW#v}"
|
||
|
||
TAG="v${VERSION_NO_V}"
|
||
echo "TAG=${TAG}"
|
||
|
||
# If tag already exists, skip creation to avoid failure
|
||
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||
echo "Tag ${TAG} already exists; skipping tag creation"
|
||
else
|
||
git tag -a "${TAG}" -m "Release ${TAG}"
|
||
git push origin "${TAG}"
|
||
fi
|
||
|
||
# Export the tag for downstream steps
|
||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
```
|
||
|
||
With:
|
||
```yaml
|
||
- name: Determine tag name
|
||
id: determine_tag
|
||
run: |
|
||
# Normalize the version: remove any leading 'v' so we don't end up with 'vvX.Y.Z'
|
||
RAW="${{ steps.semver.outputs.version }}"
|
||
VERSION_NO_V="${RAW#v}"
|
||
TAG="v${VERSION_NO_V}"
|
||
echo "Determined tag: $TAG"
|
||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||
```
|
||
|
||
**Change 2: Simplify Tag Determination** (Lines 82-93)
|
||
|
||
Delete the duplicate "Determine tag" step entirely - it's now redundant with the step above.
|
||
|
||
**Change 3: Update Release Creation** (Lines 95-105)
|
||
|
||
Replace:
|
||
```yaml
|
||
- name: Create GitHub Release (tag-only, no workspace changes)
|
||
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
|
||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||
with:
|
||
tag_name: ${{ steps.determine_tag.outputs.tag }}
|
||
name: Release ${{ steps.determine_tag.outputs.tag }}
|
||
generate_release_notes: true
|
||
make_latest: false
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
```
|
||
|
||
With:
|
||
```yaml
|
||
- name: Create GitHub Release (creates tag via API)
|
||
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
|
||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||
with:
|
||
tag_name: ${{ steps.determine_tag.outputs.tag }}
|
||
name: Release ${{ steps.determine_tag.outputs.tag }}
|
||
generate_release_notes: true
|
||
make_latest: true # Mark as latest release (changed from false)
|
||
draft: false # Publish immediately
|
||
prerelease: false # Mark as stable release
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
```
|
||
|
||
**Change 4: Add Success Confirmation**
|
||
|
||
Add at the end:
|
||
```yaml
|
||
- name: Output release information
|
||
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
|
||
run: |
|
||
echo "✅ Successfully created release: ${{ steps.determine_tag.outputs.tag }}"
|
||
echo "📦 Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.determine_tag.outputs.tag }}"
|
||
```
|
||
|
||
#### Step 3: Complete Modified Workflow
|
||
|
||
**Full modified workflow file:**
|
||
|
||
```yaml
|
||
name: Auto Versioning and Release
|
||
|
||
on:
|
||
push:
|
||
branches: [ main ]
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: false
|
||
|
||
permissions:
|
||
contents: write
|
||
pull-requests: write
|
||
|
||
jobs:
|
||
version:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Calculate Semantic Version
|
||
id: semver
|
||
uses: paulhatch/semantic-version@a8f8f59fd7f0625188492e945240f12d7ad2dca3 # v5.4.0
|
||
with:
|
||
tag_prefix: "v"
|
||
major_pattern: "/!:|BREAKING CHANGE:/"
|
||
minor_pattern: "/feat:/"
|
||
version_format: "${major}.${minor}.${patch}"
|
||
version_from_branch: "0.0.0"
|
||
search_commit_body: true
|
||
enable_prerelease_mode: false
|
||
|
||
- name: Show version
|
||
run: |
|
||
echo "Next version: ${{ steps.semver.outputs.version }}"
|
||
echo "Version changed: ${{ steps.semver.outputs.changed }}"
|
||
|
||
- name: Determine tag name
|
||
id: determine_tag
|
||
run: |
|
||
# Normalize the version: remove any leading 'v' so we don't end up with 'vvX.Y.Z'
|
||
RAW="${{ steps.semver.outputs.version }}"
|
||
VERSION_NO_V="${RAW#v}"
|
||
TAG="v${VERSION_NO_V}"
|
||
echo "Determined tag: $TAG"
|
||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||
|
||
- name: Check for existing GitHub Release
|
||
id: check_release
|
||
run: |
|
||
TAG=${{ steps.determine_tag.outputs.tag }}
|
||
echo "Checking for release for tag: ${TAG}"
|
||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||
-H "Accept: application/vnd.github+json" \
|
||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}") || true
|
||
if [ "${STATUS}" = "200" ]; then
|
||
echo "exists=true" >> $GITHUB_OUTPUT
|
||
echo "ℹ️ Release already exists for tag: ${TAG}"
|
||
else
|
||
echo "exists=false" >> $GITHUB_OUTPUT
|
||
echo "✅ No existing release found for tag: ${TAG}"
|
||
fi
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Create GitHub Release (creates tag via API)
|
||
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
|
||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||
with:
|
||
tag_name: ${{ steps.determine_tag.outputs.tag }}
|
||
name: Release ${{ steps.determine_tag.outputs.tag }}
|
||
generate_release_notes: true
|
||
make_latest: true
|
||
draft: false
|
||
prerelease: false
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Output release information
|
||
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
|
||
run: |
|
||
echo "✅ Successfully created release: ${{ steps.determine_tag.outputs.tag }}"
|
||
echo "📦 Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.determine_tag.outputs.tag }}"
|
||
```
|
||
|
||
#### Step 4: Commit and Push Changes
|
||
|
||
```bash
|
||
git add .github/workflows/auto-versioning.yml
|
||
git commit -m "fix(ci): use GitHub API for tag creation to bypass repository rules
|
||
|
||
- Replace git push with GitHub Release API
|
||
- Simplify tag creation logic
|
||
- Fix GH013 repository rule violation error
|
||
- Tag creation now happens atomically with release
|
||
|
||
Closes #[issue-number]"
|
||
git push origin main
|
||
```
|
||
|
||
#### Step 5: Monitor First Run
|
||
|
||
After pushing, monitor the workflow run:
|
||
|
||
```bash
|
||
# View workflow runs
|
||
gh run list --workflow=auto-versioning.yml
|
||
|
||
# Watch the latest run
|
||
gh run watch
|
||
```
|
||
|
||
Expected output:
|
||
```
|
||
✅ Successfully created release: v1.0.0
|
||
📦 Release URL: https://github.com/Wikid82/charon/releases/tag/v1.0.0
|
||
```
|
||
|
||
---
|
||
|
||
## Security Considerations
|
||
|
||
### Permissions Analysis
|
||
|
||
**Current Permissions:**
|
||
```yaml
|
||
permissions:
|
||
contents: write # ← Required for release creation
|
||
pull-requests: write # ← Not used in this workflow
|
||
```
|
||
|
||
**Recommendation:** Remove `pull-requests: write` as it's not used:
|
||
|
||
```yaml
|
||
permissions:
|
||
contents: write
|
||
```
|
||
|
||
### Security Improvements
|
||
|
||
| Aspect | Current | Recommended | Rationale |
|
||
|--------|---------|-------------|-----------|
|
||
| **Token Type** | `GITHUB_TOKEN` | `GITHUB_TOKEN` | Default token is sufficient and most secure |
|
||
| **Permissions** | `contents: write` + `pull-requests: write` | `contents: write` | Remove unused permissions |
|
||
| **Tag Creation** | Git push | API | API is audited and logged by GitHub |
|
||
| **Release Visibility** | Public | Public | Appropriate for public repository |
|
||
| **Secrets Required** | None | None | ✅ No additional secrets needed |
|
||
|
||
### Audit Trail
|
||
|
||
**Before (git push):**
|
||
- ✅ Git history shows tag creation
|
||
- ❌ No API audit log
|
||
- ❌ No release notes
|
||
- ❌ Blocked by repository rules
|
||
|
||
**After (API):**
|
||
- ✅ Git history shows tag creation
|
||
- ✅ GitHub API audit log
|
||
- ✅ Release notes automatically generated
|
||
- ✅ Works with repository rules
|
||
|
||
### Supply Chain Security
|
||
|
||
The recommended solution maintains supply chain security:
|
||
|
||
- ✅ **SLSA Provenance:** Release created via audited GitHub API
|
||
- ✅ **Signature:** Can add Cosign signing to release assets
|
||
- ✅ **SBOM:** No change to existing SBOM generation
|
||
- ✅ **Attestation:** GitHub Actions attestation maintained
|
||
- ✅ **Transparency:** All releases visible in GitHub UI
|
||
|
||
### Compliance
|
||
|
||
**CIS Benchmarks:**
|
||
- ✅ 3.1: Use least privilege (only `contents: write`)
|
||
- ✅ 3.2: Explicit permissions (defined in workflow)
|
||
- ✅ 3.3: No hardcoded secrets (uses `GITHUB_TOKEN`)
|
||
- ✅ 3.4: Audit logging (GitHub API logs all actions)
|
||
|
||
**OWASP:**
|
||
- ✅ A01 (Broken Access Control): Uses GitHub's access control
|
||
- ✅ A02 (Cryptographic Failures): No secrets to protect
|
||
- ✅ A07 (Identification and Authentication): GitHub manages auth
|
||
|
||
---
|
||
|
||
## Testing & Validation
|
||
|
||
### Pre-Deployment Testing
|
||
|
||
**Test 1: YAML Syntax Validation**
|
||
|
||
```bash
|
||
# Validate YAML syntax
|
||
yamllint .github/workflows/auto-versioning.yml
|
||
|
||
# GitHub Actions workflow syntax check
|
||
actionlint .github/workflows/auto-versioning.yml
|
||
```
|
||
|
||
**Expected:** ✅ No errors
|
||
|
||
**Test 2: Dry Run with `act`** (if available)
|
||
|
||
```bash
|
||
# Simulate workflow locally
|
||
act push -W .github/workflows/auto-versioning.yml --dryrun
|
||
```
|
||
|
||
**Expected:** ✅ Workflow steps parse correctly
|
||
|
||
### Post-Deployment Validation
|
||
|
||
**Test 3: Trigger Workflow with Feature Commit**
|
||
|
||
```bash
|
||
# Create a test commit that bumps minor version
|
||
git checkout -b test/versioning-fix
|
||
echo "test" > test-file.txt
|
||
git add test-file.txt
|
||
git commit -m "feat: test auto-versioning fix"
|
||
git push origin test/versioning-fix
|
||
|
||
# Create PR and merge to main
|
||
gh pr create --title "test: versioning workflow" --body "Testing auto-versioning fix"
|
||
gh pr merge --merge
|
||
```
|
||
|
||
**Expected:**
|
||
- ✅ Workflow runs without errors
|
||
- ✅ Tag created via GitHub Release
|
||
- ✅ Release published with auto-generated notes
|
||
- ✅ No git push errors
|
||
|
||
**Test 4: Verify Tag Existence**
|
||
|
||
```bash
|
||
# Fetch tags
|
||
git fetch --tags
|
||
|
||
# List tags
|
||
git tag -l "v*"
|
||
|
||
# Verify tag on GitHub
|
||
gh release list
|
||
```
|
||
|
||
**Expected:**
|
||
- ✅ Tag `v<version>` exists locally
|
||
- ✅ Tag exists on remote
|
||
- ✅ Release visible in GitHub UI
|
||
|
||
**Test 5: Verify No Duplicate Release**
|
||
|
||
```bash
|
||
# Trigger workflow again (should skip)
|
||
git commit --allow-empty -m "chore: trigger workflow"
|
||
git push origin main
|
||
```
|
||
|
||
**Expected:**
|
||
- ✅ Workflow runs
|
||
- ✅ Detects existing release
|
||
- ✅ Skips release creation step
|
||
- ✅ No errors
|
||
|
||
### Monitoring Checklist
|
||
|
||
After deployment, monitor for 24 hours:
|
||
|
||
- [ ] Workflow runs successfully on pushes to `main`
|
||
- [ ] Tags created match semantic version pattern
|
||
- [ ] Releases published with generated notes
|
||
- [ ] No duplicate releases created
|
||
- [ ] No authentication/permission errors
|
||
- [ ] Tag visibility matches expectations (public)
|
||
- [ ] Release notifications sent to watchers
|
||
|
||
---
|
||
|
||
## Rollback Plan
|
||
|
||
### Immediate Rollback (if critical issues)
|
||
|
||
**Step 1: Restore Backup**
|
||
|
||
```bash
|
||
# Restore original workflow
|
||
cp .github/workflows/auto-versioning.yml.backup .github/workflows/auto-versioning.yml
|
||
git add .github/workflows/auto-versioning.yml
|
||
git commit -m "revert: rollback auto-versioning changes due to [issue]"
|
||
git push origin main
|
||
```
|
||
|
||
**Step 2: Manual Tag Creation**
|
||
|
||
If a release is needed immediately:
|
||
|
||
```bash
|
||
# Create tag manually (requires admin access or PAT)
|
||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||
git push origin v1.0.0
|
||
|
||
# Create release manually
|
||
gh release create v1.0.0 --generate-notes
|
||
```
|
||
|
||
### Partial Rollback (modify approach)
|
||
|
||
If API approach has issues but git push works:
|
||
|
||
**Option A: Adjust Repository Rules** (requires admin)
|
||
1. Go to Settings → Rules → Rulesets
|
||
2. Temporarily disable tag protection
|
||
3. Let workflow run with git push
|
||
4. Re-enable after release
|
||
|
||
**Option B: Use PAT Approach**
|
||
1. Create fine-grained PAT with tag creation
|
||
2. Store as `AUTO_VERSION_PAT` secret
|
||
3. Update checkout to use PAT:
|
||
```yaml
|
||
- uses: actions/checkout@v6
|
||
with:
|
||
token: ${{ secrets.AUTO_VERSION_PAT }}
|
||
fetch-depth: 0
|
||
```
|
||
4. Remove API-based changes
|
||
|
||
### Recovery Procedures
|
||
|
||
**Scenario 1: Workflow creates duplicate releases**
|
||
|
||
```bash
|
||
# Delete duplicate release and tag
|
||
gh release delete v1.0.1 --yes
|
||
git push origin :refs/tags/v1.0.1
|
||
```
|
||
|
||
**Scenario 2: Tag created but release failed**
|
||
|
||
```bash
|
||
# Create release manually for existing tag
|
||
gh release create v1.0.1 --generate-notes
|
||
```
|
||
|
||
**Scenario 3: Permission errors persist**
|
||
|
||
1. Verify `GITHUB_TOKEN` permissions in workflow file
|
||
2. Check repository settings → Actions → General → Workflow permissions
|
||
3. Ensure "Read and write permissions" is enabled
|
||
4. Ensure "Allow GitHub Actions to create and approve pull requests" is enabled (if needed)
|
||
|
||
---
|
||
|
||
## Additional Recommendations
|
||
|
||
### Future Enhancements
|
||
|
||
1. **Version File Updates:**
|
||
```yaml
|
||
# After release creation, update VERSION file
|
||
- name: Update VERSION file
|
||
if: steps.semver.outputs.changed == 'true'
|
||
run: |
|
||
echo "${{ steps.determine_tag.outputs.tag }}" > VERSION
|
||
git config user.email "actions@github.com"
|
||
git config user.name "GitHub Actions"
|
||
git add VERSION
|
||
git commit -m "chore: bump version to ${{ steps.determine_tag.outputs.tag }} [skip ci]"
|
||
git push
|
||
```
|
||
|
||
2. **Changelog Generation:**
|
||
- Consider using `github-changelog-generator` or similar
|
||
- Attach generated changelog to release
|
||
|
||
3. **Notification Integration:**
|
||
- Send Slack/Discord notification on new release
|
||
- Update project board
|
||
- Trigger downstream workflows
|
||
|
||
4. **Release Asset Upload:**
|
||
- Build binary artifacts
|
||
- Upload to release as downloadable assets
|
||
- Include SHA256 checksums
|
||
|
||
### Monitoring & Alerts
|
||
|
||
**Set up GitHub Actions alerts:**
|
||
|
||
1. Go to Repository → Settings → Notifications
|
||
2. Enable "Failed workflow run notifications"
|
||
3. Add email/Slack webhook for critical workflows
|
||
|
||
**Monitor key metrics:**
|
||
- Workflow success rate
|
||
- Time to create release
|
||
- Number of failed attempts
|
||
- Tag creation patterns
|
||
|
||
### Documentation Updates
|
||
|
||
Update the following documentation:
|
||
|
||
1. **README.md**: Document new release process
|
||
2. **CONTRIBUTING.md**: Update release instructions for maintainers
|
||
3. **CHANGELOG.md**: Note the auto-versioning workflow change
|
||
4. **docs/github-setup.md**: Update CI/CD workflow documentation
|
||
|
||
---
|
||
|
||
## Conclusion
|
||
|
||
### Summary of Changes
|
||
|
||
| Aspect | Before | After |
|
||
|--------|--------|-------|
|
||
| **Tag Creation Method** | `git push` | GitHub Release API |
|
||
| **Repository Rules** | Blocked | Compatible |
|
||
| **Permissions Required** | `contents: write` + bypass | `contents: write` only |
|
||
| **Failure Mode** | Hard failure on git push | Atomic API operation |
|
||
| **Release Notes** | Manual | Auto-generated |
|
||
| **Latest Tag** | Not marked | Properly marked |
|
||
| **Audit Trail** | Git history only | Git + GitHub API logs |
|
||
|
||
### Benefits of Recommended Solution
|
||
|
||
- ✅ **Resolves Issue:** Works with repository rules without bypass
|
||
- ✅ **No Secrets Required:** Uses existing `GITHUB_TOKEN`
|
||
- ✅ **Better UX:** Automatic release notes generation
|
||
- ✅ **Industry Standard:** Follows GitHub's recommended patterns
|
||
- ✅ **Maintainable:** Simpler code, fewer edge cases
|
||
- ✅ **Secure:** Audited API calls, no permission escalation
|
||
- ✅ **Atomic:** Tag and release created together or not at all
|
||
- ✅ **Portable:** Works across different repository configurations
|
||
|
||
### Implementation Timeline
|
||
|
||
| Phase | Task | Duration | Owner |
|
||
|-------|------|----------|-------|
|
||
| 1 | Code review of remediation plan | 1 hour | Team |
|
||
| 2 | Implement workflow changes | 30 min | DevOps |
|
||
| 3 | Test in staging (if available) | 1 hour | QA |
|
||
| 4 | Deploy to production | 15 min | DevOps |
|
||
| 5 | Monitor for 24 hours | 1 day | Team |
|
||
| 6 | Document lessons learned | 1 hour | DevOps |
|
||
|
||
**Total Estimated Time:** 4 hours + 24h monitoring
|
||
|
||
### Next Steps
|
||
|
||
1. ✅ Review this remediation plan
|
||
2. ✅ Get approval from repository admin/maintainer
|
||
3. ✅ Implement workflow changes
|
||
4. ✅ Test with a non-critical commit
|
||
5. ✅ Monitor first few releases
|
||
6. ✅ Update documentation
|
||
7. ✅ Close related issues
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
### GitHub Documentation
|
||
|
||
- [Creating releases](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository)
|
||
- [Repository rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets)
|
||
- [GITHUB_TOKEN permissions](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
|
||
- [GitHub Release API](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release)
|
||
|
||
### Actions Used
|
||
|
||
- [`softprops/action-gh-release@v2`](https://github.com/softprops/action-gh-release)
|
||
- [`paulhatch/semantic-version@v5.4.0`](https://github.com/PaulHatch/semantic-version)
|
||
- [`actions/checkout@v6`](https://github.com/actions/checkout)
|
||
|
||
### Related Issues
|
||
|
||
- [Auto-versioning workflow failing on tag push](#) (create issue)
|
||
- [Repository rules blocking tag creation](#) (create issue)
|
||
|
||
---
|
||
|
||
*Remediation plan created: January 15, 2026*
|
||
*Last updated: January 15, 2026*
|
||
*Status: Ready for implementation*
|