You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Description
When a cherry-pick fails during sync PR creation, the workflow now
creates a **conflict PR** instead of hard-failing with a
`PermanentSyncError`. This unblocks the sync pipeline by giving humans
(and eventually the conflict-resolution agent) a PR artifact to work
with, rather than requiring manual RUNBOOK intervention.
The conflict PR:
- Commits the raw conflict state (with markers or modify/delete
artifacts) so reviewers see exactly what git produced.
- Invokes the conflict-resolution agent to produce a separate resolution
commit on top (currently broken — fails gracefully; will be fixed in a
follow-up).
- Adds a `Repo-Sync-Conflict: cherry-pick` trailer, `repo-sync:conflict`
label, assigns a reviewer, and sends a Slack notification.
- Continues processing remaining commits (they stack on top of the
conflict branch).
The approve workflow checks for the `Repo-Sync-Conflict` trailer and
**never** approves a PR that has it — human approval is unconditionally
required. This is a structural guarantee independent of the bot's code
being correct.
Also threads `escalate_to` through the sync workflow so the conflict
handler can determine the right reviewer/fallback team.
## Testing
Existing tests pass (251/251). The new code paths
(`_handle_cherry_pick_conflict`, approve workflow gate) interact with
git and the GitHub API at runtime — manual end-to-end testing against a
real repo pair is needed.
Co-Authored-By: Oz <oz-agent@warp.dev>
Copy file name to clipboardExpand all lines: docs/RUNBOOK.md
+64-39Lines changed: 64 additions & 39 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,11 +2,11 @@
2
2
3
3
operational procedures for repo-sync failure scenarios.
4
4
5
-
## cherry-pick failure during public-to-private sync
5
+
## cherry-pick conflict during public-to-private sync
6
6
7
7
### symptoms
8
8
9
-
the sync workflow fails with a cherry-pick conflict error. the Slack notification reads something like: `repo-sync: cherry-pick failed for <sha> in <repo>`. the watermark does not advance, so all subsequent commits are blocked.
9
+
the sync workflow creates a conflict PR with the `repo-sync:conflict` label and `Repo-Sync-Conflict` trailer. the Slack notification reads something like: `repo-sync: cherry-pick conflict for <sha> in <repo>. Conflict PR #<n> created.` a reviewer is automatically assigned.
10
10
11
11
### cause
12
12
@@ -16,95 +16,120 @@ this is rare -- it can only happen when a public commit modifies lines adjacent
16
16
17
17
### remediation
18
18
19
-
#### option 1: fix the root cause (preferred)
19
+
#### option 1: resolve the conflict PR (preferred)
20
+
21
+
the conflict PR contains the raw cherry-pick conflict (with conflict markers or modify/delete artifacts) and possibly an agent-proposed resolution commit on top.
22
+
23
+
1. check out the conflict PR's branch locally
24
+
2. resolve the conflicts (edit files to remove conflict markers, or handle modify/delete cases)
25
+
3. push the resolution as a new commit
26
+
4. approve the PR and merge it
27
+
5. subsequent sync PRs in the stack will be restacked automatically
28
+
29
+
the approve bot will **never** auto-approve a conflict PR (the `Repo-Sync-Conflict` trailer prevents this). human approval is always required.
30
+
31
+
#### option 2: fix the root cause
20
32
21
33
adjust the private repo so the cherry-pick context matches:
22
34
23
-
1. identify the conflicting file and lines from the workflow logs
35
+
1. identify the conflicting file and lines from the conflict PR
24
36
2. in the private repo, move the nearby `!repo-sync` marker regions so they don't overlap with the public commit's diff context. options:
25
37
- move the private-only code to a `private/` directory instead of using inline markers
26
38
- rearrange the code so the marker region is further from the modified lines
27
39
3. merge the fix into the private repo's default branch
28
-
4. the next sync workflow run retries the cherry-pick against the updated `main`. if the context now matches, the cherry-pick succeeds and the pipeline unblocks automatically
40
+
4.close the conflict PR. the next sync workflow run retries the cherry-pick against the updated `main`. if the context now matches, the cherry-pick succeeds and creates a clean PR
29
41
30
-
#### option 2: manually create the sync PR (escape hatch)
42
+
#### option 3: manually create the sync PR (escape hatch)
31
43
32
-
if fixing the root cause isn't practical or is too slow:
44
+
if the conflict PR is unusable for some reason:
33
45
34
-
1. determine the source commit SHA and the sync branch name from the workflow logs (format: `repo-sync/public-to-private/<short-sha>`)
35
-
2. check out the private repo locally
36
-
3. create the sync branch from the current stack top (or `main` if no stack):
46
+
1. close the conflict PR
47
+
2. determine the source commit SHA and the sync branch name from the workflow logs (format: `repo-sync/public-to-private/<short-sha>`)
48
+
3. check out the private repo locally
49
+
4. create the sync branch from the current stack top (or `main` if no stack):
6. push the branch and create the PR with the correct `Repo-Sync-Origin` trailer in the description
51
-
7. the sync workflow's idempotency guard will see the branch exists and skip this commit on the next run, unblocking the pipeline
63
+
7. push the branch and create the PR with the correct `Repo-Sync-Origin` trailer in the description
64
+
8. the sync workflow's idempotency guard will see the branch exists and skip this commit on the next run, unblocking the pipeline
52
65
53
66
### key properties
54
67
55
-
- the watermark does not advance past the failing commit, so no commits are lost
68
+
- the watermark does not advance past the conflicting commit, so no commits are lost
69
+
- subsequent commits are stacked on top of the conflict PR and will be processed once it merges
56
70
- the idempotency guard ensures manual intervention does not conflict with automation
57
-
- once the failing commit is handled (by either path), the sync workflow processes all remaining unsynced commits automatically
71
+
- once the conflict PR is resolved and merged, the sync workflow processes all remaining unsynced commits automatically
58
72
59
-
## patch apply failure during private-to-public sync
73
+
## cherry-pick conflict during private-to-public sync
60
74
61
75
### symptoms
62
76
63
-
the sync workflow fails with a patch apply error. the Slack notification reads something like: `repo-sync: patch apply failed for <sha> in <repo>. Un-synced public changes overlap.`the watermark does not advance, so all subsequent commits are blocked.
77
+
the sync workflow creates a conflict PR with the `repo-sync:conflict` label and `Repo-Sync-Conflict` trailer. the Slack notification reads something like: `repo-sync: cherry-pick conflict for <sha> in <repo>. Conflict PR #<n> created.`a reviewer is automatically assigned.
64
78
65
79
### cause
66
80
67
-
a private commit modifies lines in a file that also has un-synced public changes nearby. the sync workflow generates a patch (diff between consecutive clean snapshots of the private repo) and applies it to the public repo. if the public repo's state in the affected area differs from the clean snapshot's context (because of un-synced public changes), `git apply` fails.
81
+
a private commit modifies lines in a file that also has un-synced public changes nearby. the sync workflow generates a clean delta (diff between consecutive stripped snapshots of the private repo) and cherry-picks it onto the public repo. if the public repo's state in the affected area differs from the clean snapshot's context (because of un-synced public changes), the cherry-pick fails.
68
82
69
83
this happens when both repos independently modify the same area of a file before the changes have been synced in both directions.
70
84
71
85
### remediation
72
86
73
-
#### option 1: merge the pending public-to-private sync PRs first (preferred)
87
+
#### option 1: resolve the conflict PR (preferred)
88
+
89
+
the conflict PR contains the raw cherry-pick conflict (with conflict markers or modify/delete artifacts) and possibly an agent-proposed resolution commit on top.
74
90
75
-
the most common cause is pending public-to-private sync PRs that haven't merged yet. once they merge, the private repo includes the public changes, and the next sync run generates a patch with the correct context.
91
+
1. check out the conflict PR's branch locally
92
+
2. resolve the conflicts (edit files to remove conflict markers, or handle modify/delete cases)
93
+
3. push the resolution as a new commit
94
+
4. approve the PR and merge it
95
+
5. subsequent sync PRs in the stack will be restacked automatically
76
96
77
-
1. check for open public-to-private sync PRs in the private repo
78
-
2. merge them (or wait for them to auto-merge)
79
-
3. the next private-to-public sync run retries with updated context and should succeed
97
+
the approve bot will **never** auto-approve a conflict PR (the `Repo-Sync-Conflict` trailer prevents this). human approval is always required.
80
98
81
-
#### option 2: manually create the sync PR (escape hatch)
if merging pending sync PRs doesn't resolve the issue:
101
+
the most common root cause is pending public-to-private sync PRs that haven't merged yet. once they merge, the private repo includes the public changes, and a subsequent retry would generate a patch with the correct context.
84
102
85
-
1. determine the source commit SHA and sync branch name from the workflow logs (format: `repo-sync/private-to-public/<short-sha>`)
86
-
2. check out the public repo locally
87
-
3. create the sync branch from the current stack top (or `main` if no stack):
103
+
1. close the conflict PR
104
+
2. check for open public-to-private sync PRs in the private repo
105
+
3. merge them (or wait for them to auto-merge)
106
+
4. the next private-to-public sync run retries with updated context and should succeed
107
+
108
+
#### option 3: manually create the sync PR (escape hatch)
109
+
110
+
if the conflict PR is unusable for some reason:
111
+
112
+
1. close the conflict PR
113
+
2. determine the source commit SHA and sync branch name from the workflow logs (format: `repo-sync/private-to-public/<short-sha>`)
114
+
3. check out the public repo locally
115
+
4. create the sync branch from the current stack top (or `main` if no stack):
Copy file name to clipboardExpand all lines: docs/TECH-DESIGN.md
+15-2Lines changed: 15 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -35,19 +35,31 @@ when a workflow run starts, it reads the watermark tag to determine the last-syn
35
35
b. compute the diff between the two clean snapshots by committing both into a temporary git repo. if the diff is empty, skip (all changes in this commit were internal-only)
36
36
c. check if a PR with head branch `repo-sync/private-to-public/<short-sha>` was previously created (idempotency guard -- prevents duplicates if the workflow crashed and restarted mid-run). if a MERGED PR exists, skip without updating the stack base. if an OPEN PR exists, use its branch as the stack base and skip
37
37
d. create a branch `repo-sync/private-to-public/<short-sha>` based on the top of the current stack (or `main` if no stack)
38
-
e. apply the delta to the public repo by cherry-picking the diff commit from the temporary repo. cherry-pick uses three-way merge internally, so it handles context mismatches from un-synced public changes gracefully. if the cherry-pick produces no changes (delta already present in the public repo), skip. if it conflicts, fail loudly
38
+
e. apply the delta to the public repo by cherry-picking the diff commit from the temporary repo. cherry-pick uses three-way merge internally, so it handles context mismatches from un-synced public changes gracefully. if the cherry-pick produces no changes (delta already present in the public repo), skip. if it conflicts, create a **conflict PR** (see below)
39
39
f. amend the commit with a generic message (e.g., `"repo-sync: sync from private"`) and the `Repo-Sync-Origin` trailer. **do not** use the source commit's message, as it could leak private information
40
40
g. push the branch. if the branch already exists on the remote (from a previous interrupted run where push succeeded but PR creation failed), verify the content matches and skip the push. hard-fail if the content differs
41
41
h. create a PR with the base set to the previous sync branch (or `main`)
42
42
43
43
the sync workflow does **not** approve or enable auto-merge on any PRs. approval is handled by the separate approve workflow when a PR reaches the bottom of the stack.
44
44
45
+
**cherry-pick conflict PRs:**
46
+
47
+
when a cherry-pick fails during sync PR creation (either direction), instead of failing the workflow, the sync creates a conflict PR:
48
+
1. commit the raw conflict state as-is (with conflict markers or modify/delete artifacts) using `git add -A` + `git commit`. this gives reviewers full visibility into the raw conflict
49
+
2. invoke the conflict-resolution agent to produce a **separate resolution commit** on top. if the agent fails (or is unavailable), the branch is left with just the raw conflict commit
50
+
3. push the branch and create the PR with a `Repo-Sync-Conflict: cherry-pick` trailer, the `repo-sync:conflict` label, and a `Repo-Sync-Assigned` trailer
51
+
4. request review from the person who merged the source PR (or the fallback team)
52
+
5. send a Slack notification with the conflict PR number
53
+
6. continue processing remaining unsynced commits (they stack on top of the conflict branch)
54
+
55
+
the `Repo-Sync-Conflict` trailer is a structural guarantee: the approve workflow checks for it and **never** approves a PR with this trailer. conflict PRs unconditionally require human approval, regardless of mergeability state. this ensures no conflict PR is auto-merged without human review.
56
+
45
57
**public-to-private:**
46
58
1. identify unsynced commits on the public repo's default branch
47
59
2. for each unsynced commit (in chronological order):
48
60
a. check if a branch `repo-sync/public-to-private/<short-sha>` already exists or a PR with that head branch was previously created (idempotency guard)
49
61
b. create a branch `repo-sync/public-to-private/<short-sha>` based on the top of the current stack (or `main` if no stack)
50
-
c. cherry-pick the commit, preserving author and message. if the cherry-pick fails (rare -- caused by private-only code overlapping with the public commit's diff context), the workflow**fails loudly**and notifies oncall. see [RUNBOOK.md](RUNBOOK.md) for remediation steps
62
+
c. cherry-pick the commit, preserving author and message. if the cherry-pick fails (rare -- caused by private-only code overlapping with the public commit's diff context), create a**conflict PR**(see below)
51
63
d. create a PR with the base set to the previous sync branch (or `main`)
52
64
53
65
### merge strategy
@@ -94,6 +106,7 @@ the workflow is serialized per PR via a concurrency group (`cancel-in-progress:
94
106
95
107
**decision logic (in order):**
96
108
1.**already handled?** if the PR has an existing approval OR a `Repo-Sync-Assigned` trailer → skip
109
+
1b. **conflict PR?** if the PR has a `Repo-Sync-Conflict` trailer → skip. these PRs unconditionally require human approval
97
110
2.**commit count?** if the PR has ≠ 1 commit → skip. the restack workflow handles restacking (either post-merge or via the `repo-sync:needs-restack` label, which can be added manually or by the escalation cron)
98
111
3.**mergeable?** check via GitHub API (with retries for `UNKNOWN`):
99
112
-`MERGEABLE` → approve + enable auto-merge (API-only, no git operations)
0 commit comments