ADR-031: Template Sync Duplicate Prevention Pattern
Status
Accepted
Context
Following the successful implementation of duplicate prevention for upstream sync workflows (ADR-024), the same problem was identified in the template sync workflow. The daily template sync workflow (sync-template.yml) was creating duplicate PRs when humans delayed reviewing PRs, causing:
- Duplicate template-sync PRs - Multiple open PRs for the same template updates
- Repository clutter - Accumulation of stale template-sync branches
- Notification fatigue - Redundant GitHub notifications for template updates
- Confusion - Unclear which PR contains the latest template changes
Evidence of the Problem:
In production fork repositories, template-sync was creating a new PR every day when changes existed, even if a previous PR was still open. This resulted in 6+ open template-sync PRs simultaneously (e.g., PRs #12-18 in danielscholl-osdu/workflow).
Relationship to ADR-024:
ADR-024 solved this problem for upstream sync using the sync-state-manager action. Template sync needed the same duplicate prevention pattern, but with a simpler inline implementation.
Decision
Implement Template Sync Duplicate Prevention following the same architectural pattern as upstream sync (ADR-024), but with inline detection logic instead of using the sync-state-manager action:
1. PR Detection and Label-Based Tracking
- New Label:
template-syncadded to.github/labels.json - Detection Logic: Query GitHub API for open PRs with
template-synclabel targetingmainbranch - Inline Implementation: Detection logic embedded directly in workflow (not extracted to action)
2. Decision Matrix (Identical to Upstream Sync)
| Existing PR | Template Changed | Action |
|-------------|------------------|---------------------------|
| No | Yes | Create new PR |
| Yes | No | No action needed |
| Yes | Yes | Update existing branch/PR |
| No | No | No action needed |
3. Branch Reuse Strategy
- Existing PR Found: Reuse existing branch, reset to
main, force-push updates - No PR Found: Create new timestamped branch (
template-sync/YYYYMMDD-HHMMSS) - Single Active PR: Only one template-sync PR open at any time
4. PR Update Behavior
When updating an existing PR: - Force-push new commits to existing branch - Update the PR title to chore(template-sync): sync template updates (updated YYYY-MM-DD); the conventional prefix stays first because the semantic PR check reads the type from it - Regenerate PR description with all current changes - Post a comment recording the new template commit and update time
Rationale
Why Not Reuse sync-state-manager Action?
Decision: Inline Implementation
The sync-state-manager action was designed for upstream sync's more complex state management needs (tracking upstream SHA, issue lifecycle, cascade coordination). Template sync has simpler requirements:
- Simpler State: Only needs to track PR existence, not upstream versions
- No Issue Tracking: Template sync doesn't create tracking issues like upstream sync
- Different Base Branch: Targets
maininstead offork_upstream - Self-Contained: All logic fits naturally in workflow without external scripts
Implementation Details
Detection Step (New)
- name: Detect existing template sync PRs
id: detect-existing
if: steps.check-updates.outputs.has_updates == 'true'
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
echo "Detecting existing template sync PRs..."
# Query for open PRs with template-sync label targeting main branch
EXISTING_PR=$(gh pr list \
--state open \
--label "template-sync" \
--base main \
--json number,headRefName \
--jq '.[0]')
if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
PR_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number')
PR_BRANCH=$(echo "$EXISTING_PR" | jq -r '.headRefName')
echo "has_existing_pr=true" >> $GITHUB_OUTPUT
echo "existing_pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "existing_pr_branch=$PR_BRANCH" >> $GITHUB_OUTPUT
else
echo "has_existing_pr=false" >> $GITHUB_OUTPUT
fi
Branch Management (Modified)
- name: Create or update template sync branch
if: steps.check-updates.outputs.has_updates == 'true'
run: |
if [ "${{ steps.detect-existing.outputs.has_existing_pr }}" = "true" ]; then
# Reuse existing branch
SYNC_BRANCH="${{ steps.detect-existing.outputs.existing_pr_branch }}"
git fetch origin $SYNC_BRANCH
git checkout -b $SYNC_BRANCH origin/$SYNC_BRANCH
git reset --hard refs/heads/main
else
# Create new sync branch with timestamp
DATE_SUFFIX=$(date +%Y%m%d-%H%M%S)
SYNC_BRANCH="template-sync/${DATE_SUFFIX}"
git checkout -b $SYNC_BRANCH refs/heads/main
fi
echo "SYNC_BRANCH=$SYNC_BRANCH" >> $GITHUB_ENV
Split PR Creation (New)
PR creation originally went through a create-enhanced-pr action that also produced an AI-written description; #162 removed that action (ADR-014) and both paths now call gh directly with a body computed from the diff.
- name: Create new template sync PR
if: steps.check-updates.outputs.has_updates == 'true' &&
env.has_changes == 'true' &&
steps.detect-existing.outputs.has_existing_pr == 'false'
id: create-pr
# gh pr create --body-file with the template-sync label; see sync-template.yml
- name: Update existing template sync PR
if: steps.check-updates.outputs.has_updates == 'true' &&
env.has_changes == 'true' &&
steps.detect-existing.outputs.has_existing_pr == 'true'
id: update-pr
# gh pr edit --title/--body-file, then gh pr comment; see sync-template.yml
Label Definition
{
"name": "template-sync",
"description": "Template repository synchronization",
"color": "0e8a16"
}
Consequences
Detection logic is a simplified copy of what sync-state-manager does for upstream sync, so the two implementations can drift apart and each workflow manages its own state.
Comparison with ADR-024
| Aspect | Upstream Sync (ADR-024) | Template Sync (ADR-031) |
|---|---|---|
| Pattern | Duplicate prevention via state management | Same pattern |
| Implementation | Dedicated action (sync-state-manager) | Inline detection logic |
| Label | upstream-sync | template-sync |
| Base Branch | fork_upstream | main |
| State Tracking | Full-SHA issue marker + PR/issue labels | PR detection only |
| Complexity | Higher (cascade coordination) | Lower (PR-only) |
| Reusability | Action reusable by other workflows | Workflow-specific logic |
References
- ADR-024: Sync Workflow Duplicate Prevention Architecture - Original pattern
- ADR-012: Template Update Propagation Strategy - Template sync workflow
- ADR-011: Configuration-Driven Template Synchronization - Template sync foundation