Workflow Cycle Prevention Fix
Problem Summary
Section titled “Problem Summary”The project was experiencing excessive version releases due to a cyclic dependency between two GitHub workflows:
smart-version-bump.yml- Triggers on main branch pushessync-package-version.yml- Triggers on tag creation
The Problematic Flow
Section titled “The Problematic Flow”- Main branch push → triggers
smart-version-bump.yml - Smart version bump creates tag and pushes commit → triggers
sync-package-version.yml - Sync workflow creates PR and merges back to main → potentially triggers step 1 again
This created a cycle where workflows would trigger each other, leading to multiple version releases.
Solution Implemented
Section titled “Solution Implemented”Option 2: Conditional Approach with Safeguards
Section titled “Option 2: Conditional Approach with Safeguards”Instead of removing the sync workflow entirely, we implemented comprehensive safeguards to prevent the cycle while maintaining both workflows for different scenarios.
Changes Made
Section titled “Changes Made”1. Smart Version Bump Workflow (smart-version-bump.yml)
Section titled “1. Smart Version Bump Workflow (smart-version-bump.yml)”Path Filtering
Section titled “Path Filtering”on: push: branches: [main] paths-ignore: - '.github/**' - 'docs/**' - '*.md'- Prevents workflow from triggering on documentation or workflow file changes
Workflow-Generated Commit Detection
Section titled “Workflow-Generated Commit Detection”# Check if the latest commit is a workflow-generated commitLATEST_COMMIT_MSG=$(git log -1 --pretty=format:"%s")if [[ "$LATEST_COMMIT_MSG" =~ ^chore:\ (bump\ version|sync\ package\.json\ version) ]] || [[ "$LATEST_COMMIT_MSG" =~ \[skip\ ci\] ]]; then echo "🛑 Skipping version bump to prevent cycle" exit 0fi- Detects commits made by workflows and skips version bumping
Concurrency Control
Section titled “Concurrency Control”concurrency: group: version-bump-${{ github.ref }} cancel-in-progress: false- Prevents multiple instances from running simultaneously
2. Sync Package Version Workflow (sync-package-version.yml)
Section titled “2. Sync Package Version Workflow (sync-package-version.yml)”Pre-check Job
Section titled “Pre-check Job”jobs: check-sync-needed: name: Check if version sync is needed outputs: sync_needed: ${{ steps.check.outputs.sync_needed }}- Adds a job that checks if sync is actually needed before running the main job
Conditional Execution
Section titled “Conditional Execution”sync-package-version: needs: check-sync-needed if: needs.check-sync-needed.outputs.sync_needed == 'true'- Only runs the sync job if package.json version doesn’t match the tag
Manual Trigger for Testing
Section titled “Manual Trigger for Testing”workflow_dispatch: inputs: tag: description: 'Tag to sync (e.g., v1.2.3)' required: true type: string- Allows manual testing of the sync workflow
3. Sync Script (sync-version.cjs)
Section titled “3. Sync Script (sync-version.cjs)”Race Condition Protection
Section titled “Race Condition Protection”// Check if this tag was created very recently (potential race condition)const tagDate = exec(`git log -1 --format=%ct ${tag} 2>/dev/null`);const currentTime = Math.floor(Date.now() / 1000);const tagAge = currentTime - parseInt(tagDate);
if (tagAge < 30) { // Less than 30 seconds old console.log('⏳ Waiting 30 seconds to ensure tag creation is complete...'); await new Promise((resolve) => setTimeout(resolve, 30000));}- Adds a 30-second delay for very recent tags to prevent race conditions
Enhanced Skip CI
Section titled “Enhanced Skip CI”git commit -m "chore: sync package.json version to ${version} [skip ci]
Automatically generated by tag ${tag}This commit should not trigger additional workflows.
[skip ci]"- More aggressive use of
[skip ci]to prevent workflow retriggering
Expected Behavior
Section titled “Expected Behavior”Normal Flow (Primary Path)
Section titled “Normal Flow (Primary Path)”- PR merged to main → Smart version bump workflow runs
- Smart version bump → Updates package.json and creates tag in single commit
- Tag creation → Sync workflow checks → Finds package.json already correct → Skips
Legacy/Fallback Flow
Section titled “Legacy/Fallback Flow”- Manual tag creation (without package.json update) → Sync workflow runs
- Sync workflow → Creates PR with
[skip ci]→ Merges without retriggering version bump
Workflow-Generated Commits
Section titled “Workflow-Generated Commits”- Workflow commit pushed → Smart version bump detects workflow pattern → Skips
- No additional version bumps triggered by workflow maintenance commits
Benefits
Section titled “Benefits”- Prevents Cycles: Multiple safeguards prevent workflows from triggering each other
- Maintains Flexibility: Both workflows remain available for different scenarios
- Race Condition Protection: Timing delays and concurrency controls prevent conflicts
- Clear Separation: Primary vs fallback paths are well defined
- Testable: Manual triggers allow for testing workflow behavior
Monitoring
Section titled “Monitoring”After deployment, monitor for:
- Reduced frequency of version releases
- No workflow-generated commits triggering additional bumps
- Sync workflow skipping when not needed
- Proper functioning of the primary smart version bump flow
Rollback Plan
Section titled “Rollback Plan”If issues persist, the changes can be rolled back and we can implement Option 1 (removing the sync workflow entirely) as the sync functionality is now redundant with the smart version bump workflow.