Skip to content

Commit 0ef82b7

Browse files
committed
fix: resolve GitHub Actions permission issues for automated CI/CD pipeline
**Permission Fixes:** - Add explicit workflow permissions (contents: write, actions: write, packages: write) - Configure proper git authentication using GITHUB_TOKEN with x-access-token format - Use official github-actions[bot] email and name for commits - Enable persist-credentials for checkout action to maintain authentication **Enhanced Error Handling:** - Add retry logic for git push operations (3 attempts with 5-second delays) - Check for existing tags before creation to prevent conflicts - Validate changes before committing to avoid empty commits - Provide detailed error messages for troubleshooting **Comprehensive Documentation:** - Create GitHub Actions Setup Guide (docs/GITHUB_ACTIONS_SETUP.md) - Document repository settings configuration steps - Explain GITHUB_TOKEN vs Personal Access Token differences - Provide troubleshooting guide for common permission issues **Diagnostic Tools:** - Create permission diagnostic script (scripts/diagnose_permissions.py) - Add alternative workflow example using Personal Access Token - Include git operations simulation for testing permissions - Provide verification checklist for proper configuration **Expected Resolution:** - Automated pipeline should now successfully push version bump commits - Git tags should be created and pushed without permission errors - Full release process should complete: version bump tag build publish release - No more 'Permission denied' or 'Resource not accessible' errors **Root Cause:** GitHub Actions bot lacked write permissions to push commits and tags back to repository. Default GITHUB_TOKEN permissions were insufficient for automated git operations. **Solution:** Explicit workflow permissions + proper git authentication configuration + comprehensive error handling and documentation.
1 parent 314eba1 commit 0ef82b7

4 files changed

Lines changed: 820 additions & 7 deletions

File tree

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Alternative Automated Release Pipeline using Personal Access Token
2+
#
3+
# This is an example workflow that uses a Personal Access Token instead of GITHUB_TOKEN
4+
# Use this if the main workflow fails due to permission issues that can't be resolved
5+
# with repository settings.
6+
#
7+
# To use this workflow:
8+
# 1. Create a Personal Access Token with 'repo' and 'workflow' scopes
9+
# 2. Add it as a repository secret named 'PERSONAL_ACCESS_TOKEN'
10+
# 3. Rename this file to 'auto-release.yml' (replacing the existing one)
11+
12+
name: Automated Release Pipeline (PAT)
13+
14+
on:
15+
push:
16+
branches: [ main ]
17+
paths-ignore:
18+
- 'README.md'
19+
- 'docs/**'
20+
- '.gitignore'
21+
- 'LICENSE'
22+
23+
# Explicit permissions (still needed even with PAT)
24+
permissions:
25+
contents: write
26+
actions: write
27+
packages: write
28+
pull-requests: write
29+
issues: write
30+
31+
env:
32+
PYTHON_VERSION: '3.11'
33+
34+
jobs:
35+
analyze-and-version:
36+
runs-on: ubuntu-latest
37+
outputs:
38+
should_release: ${{ steps.version_check.outputs.should_release }}
39+
version_type: ${{ steps.version_check.outputs.version_type }}
40+
new_version: ${{ steps.version_check.outputs.new_version }}
41+
current_version: ${{ steps.version_check.outputs.current_version }}
42+
43+
steps:
44+
- uses: actions/checkout@v4
45+
with:
46+
fetch-depth: 0
47+
# Use Personal Access Token instead of GITHUB_TOKEN
48+
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
49+
persist-credentials: true
50+
51+
- name: Set up Python
52+
uses: actions/setup-python@v5
53+
with:
54+
python-version: ${{ env.PYTHON_VERSION }}
55+
56+
- name: Configure Git with PAT
57+
run: |
58+
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
59+
git config --local user.name "github-actions[bot]"
60+
# Configure git to use the Personal Access Token
61+
git config --local url."https://x-access-token:${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/".insteadOf "https://github.com/"
62+
63+
- name: Set up Rust
64+
uses: dtolnay/rust-toolchain@stable
65+
with:
66+
components: rustfmt, clippy
67+
68+
- name: Check Rust formatting
69+
run: cargo fmt --all -- --check
70+
71+
- name: Run Rust linting
72+
run: cargo clippy --all-targets --all-features -- -D warnings
73+
74+
- name: Analyze commits and determine version bump
75+
id: version_check
76+
run: |
77+
# Get current version
78+
CURRENT_VERSION=$(python scripts/get_version.py)
79+
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
80+
echo "Current version: $CURRENT_VERSION"
81+
82+
# Get commits since last tag
83+
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
84+
if [ -z "$LAST_TAG" ]; then
85+
COMMITS=$(git log --oneline)
86+
else
87+
COMMITS=$(git log ${LAST_TAG}..HEAD --oneline)
88+
fi
89+
90+
echo "Analyzing commits since $LAST_TAG:"
91+
echo "$COMMITS"
92+
93+
# Determine version bump type based on commit messages
94+
VERSION_TYPE="none"
95+
96+
# Check for breaking changes (major version)
97+
if echo "$COMMITS" | grep -qiE "(BREAKING CHANGE|breaking:|major:)"; then
98+
VERSION_TYPE="major"
99+
# Check for new features (minor version)
100+
elif echo "$COMMITS" | grep -qiE "(feat:|feature:|minor:)"; then
101+
VERSION_TYPE="minor"
102+
# Check for bug fixes and other changes (patch version)
103+
elif echo "$COMMITS" | grep -qiE "(fix:|patch:|chore:|docs:|style:|refactor:|perf:|test:)"; then
104+
VERSION_TYPE="patch"
105+
fi
106+
107+
# Skip release if no relevant changes
108+
if [ "$VERSION_TYPE" = "none" ]; then
109+
echo "No version-relevant changes detected. Skipping release."
110+
echo "should_release=false" >> $GITHUB_OUTPUT
111+
echo "version_type=none" >> $GITHUB_OUTPUT
112+
echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
113+
exit 0
114+
fi
115+
116+
echo "Determined version bump type: $VERSION_TYPE"
117+
echo "should_release=true" >> $GITHUB_OUTPUT
118+
echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT
119+
120+
# Calculate new version
121+
python scripts/bump_version.py $VERSION_TYPE
122+
NEW_VERSION=$(python scripts/get_version.py)
123+
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
124+
echo "New version will be: $NEW_VERSION"
125+
126+
- name: Commit version changes
127+
if: steps.version_check.outputs.should_release == 'true'
128+
run: |
129+
# Check if there are changes to commit
130+
if git diff --staged --quiet && git diff --quiet; then
131+
echo "No changes to commit"
132+
else
133+
echo "Committing version changes..."
134+
git add -A
135+
git commit -m "chore: bump version to ${{ steps.version_check.outputs.new_version }} [skip ci]"
136+
137+
# Push with retry logic using PAT
138+
for i in {1..3}; do
139+
if git push origin main; then
140+
echo "✅ Successfully pushed version bump commit"
141+
break
142+
else
143+
echo "❌ Push attempt $i failed, retrying in 5 seconds..."
144+
sleep 5
145+
fi
146+
147+
if [ $i -eq 3 ]; then
148+
echo "❌ Failed to push after 3 attempts"
149+
echo "Token permissions: ${{ secrets.PERSONAL_ACCESS_TOKEN && 'PAT configured' || 'PAT missing' }}"
150+
exit 1
151+
fi
152+
done
153+
fi
154+
155+
- name: Create and push tag
156+
if: steps.version_check.outputs.should_release == 'true'
157+
run: |
158+
TAG_NAME="v${{ steps.version_check.outputs.new_version }}"
159+
echo "Creating tag: $TAG_NAME"
160+
161+
# Check if tag already exists
162+
if git tag -l | grep -q "^$TAG_NAME$"; then
163+
echo "⚠️ Tag $TAG_NAME already exists, skipping tag creation"
164+
else
165+
git tag "$TAG_NAME"
166+
echo "✅ Created tag: $TAG_NAME"
167+
168+
# Push tag with retry logic using PAT
169+
for i in {1..3}; do
170+
if git push origin "$TAG_NAME"; then
171+
echo "✅ Successfully pushed tag: $TAG_NAME"
172+
break
173+
else
174+
echo "❌ Tag push attempt $i failed, retrying in 5 seconds..."
175+
sleep 5
176+
fi
177+
178+
if [ $i -eq 3 ]; then
179+
echo "❌ Failed to push tag after 3 attempts"
180+
exit 1
181+
fi
182+
done
183+
fi
184+
185+
# Rest of the workflow remains the same as the main auto-release.yml
186+
# (build-and-release, build-sdist, publish, create-release jobs)
187+
188+
build-and-release:
189+
needs: analyze-and-version
190+
if: needs.analyze-and-version.outputs.should_release == 'true'
191+
runs-on: ${{ matrix.os }}
192+
strategy:
193+
matrix:
194+
os: [ubuntu-latest, windows-latest, macos-latest]
195+
196+
steps:
197+
- uses: actions/checkout@v4
198+
with:
199+
ref: v${{ needs.analyze-and-version.outputs.new_version }}
200+
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
201+
202+
- name: Set up Python
203+
uses: actions/setup-python@v5
204+
with:
205+
python-version: ${{ env.PYTHON_VERSION }}
206+
207+
- name: Set up Rust
208+
uses: dtolnay/rust-toolchain@stable
209+
with:
210+
components: rustfmt, clippy
211+
212+
- name: Check Rust formatting
213+
run: cargo fmt --all -- --check
214+
215+
- name: Run Rust linting
216+
run: cargo clippy --all-targets --all-features -- -D warnings
217+
218+
- name: Install maturin
219+
run: pip install maturin
220+
221+
- name: Build wheels
222+
run: maturin build --release --out dist --find-interpreter
223+
224+
- name: Upload wheels
225+
uses: actions/upload-artifact@v4
226+
with:
227+
name: wheels-${{ matrix.os }}
228+
path: dist/*.whl
229+
230+
# Additional jobs would continue here...
231+
# This is a simplified example focusing on the permission fix

.github/workflows/auto-release.yml

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ on:
99
- '.gitignore'
1010
- 'LICENSE'
1111

12+
# Grant necessary permissions for the workflow
13+
permissions:
14+
contents: write # Required to push commits and create tags
15+
actions: write # Required to trigger other workflows
16+
packages: write # Required for package publishing
17+
pull-requests: write # Required for PR operations
18+
issues: write # Required for issue operations
19+
1220
env:
1321
PYTHON_VERSION: '3.11'
1422

@@ -26,6 +34,8 @@ jobs:
2634
with:
2735
fetch-depth: 0
2836
token: ${{ secrets.GITHUB_TOKEN }}
37+
# Ensure the token can push to protected branches
38+
persist-credentials: true
2939

3040
- name: Set up Python
3141
uses: actions/setup-python@v5
@@ -34,8 +44,10 @@ jobs:
3444

3545
- name: Configure Git
3646
run: |
37-
git config --local user.email "action@github.com"
38-
git config --local user.name "GitHub Action"
47+
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
48+
git config --local user.name "github-actions[bot]"
49+
# Configure git to use the GitHub token for authentication
50+
git config --local url."https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/"
3951
4052
- name: Set up Rust
4153
uses: dtolnay/rust-toolchain@stable
@@ -103,15 +115,60 @@ jobs:
103115
- name: Commit version changes
104116
if: steps.version_check.outputs.should_release == 'true'
105117
run: |
106-
git add -A
107-
git commit -m "chore: bump version to ${{ steps.version_check.outputs.new_version }} [skip ci]"
108-
git push origin main
118+
# Check if there are changes to commit
119+
if git diff --staged --quiet; then
120+
echo "No changes to commit"
121+
else
122+
echo "Committing version changes..."
123+
git add -A
124+
git commit -m "chore: bump version to ${{ steps.version_check.outputs.new_version }} [skip ci]"
125+
126+
# Push with retry logic
127+
for i in {1..3}; do
128+
if git push origin main; then
129+
echo "✅ Successfully pushed version bump commit"
130+
break
131+
else
132+
echo "❌ Push attempt $i failed, retrying in 5 seconds..."
133+
sleep 5
134+
fi
135+
136+
if [ $i -eq 3 ]; then
137+
echo "❌ Failed to push after 3 attempts"
138+
exit 1
139+
fi
140+
done
141+
fi
109142
110143
- name: Create and push tag
111144
if: steps.version_check.outputs.should_release == 'true'
112145
run: |
113-
git tag "v${{ steps.version_check.outputs.new_version }}"
114-
git push origin "v${{ steps.version_check.outputs.new_version }}"
146+
TAG_NAME="v${{ steps.version_check.outputs.new_version }}"
147+
echo "Creating tag: $TAG_NAME"
148+
149+
# Check if tag already exists
150+
if git tag -l | grep -q "^$TAG_NAME$"; then
151+
echo "⚠️ Tag $TAG_NAME already exists, skipping tag creation"
152+
else
153+
git tag "$TAG_NAME"
154+
echo "✅ Created tag: $TAG_NAME"
155+
156+
# Push tag with retry logic
157+
for i in {1..3}; do
158+
if git push origin "$TAG_NAME"; then
159+
echo "✅ Successfully pushed tag: $TAG_NAME"
160+
break
161+
else
162+
echo "❌ Tag push attempt $i failed, retrying in 5 seconds..."
163+
sleep 5
164+
fi
165+
166+
if [ $i -eq 3 ]; then
167+
echo "❌ Failed to push tag after 3 attempts"
168+
exit 1
169+
fi
170+
done
171+
fi
115172
116173
build-and-release:
117174
needs: analyze-and-version

0 commit comments

Comments
 (0)