mr-watch: detect branch/main conflicts and route to escalate
🔍 Problem
The mr-watch skill (.claude/skills/mr-watch/) watches an MR for reviewer feedback and CI status, but is blind to the branch falling into merge conflict with the target. When main advances and the branch conflicts, the watcher keeps reporting quiet/clean ticks while the MR is unmergeable — the conflict only surfaces when a human notices.
Hit live on !400 (merged): main advanced 20 commits (including a Config proto change), the branch developed a conflict in config.proto / config.pb.go, and nothing in the watch loop flagged it.
✨ Proposed change — detect, then escalate
Add conflict detection to the per-iteration flow and route a positive detection to the existing escalation path. Do not add autonomous conflict resolution.
Detection (safe, cheap)
- After the exit-gate check, read
has_conflictsanddetailed_merge_statusfrom the MR payload (glab api .../merge_requests/<iid>) — fields already present, no extra API calls. - Surface a
Conflictsrow in all three report templates (assets/reports/{quiet,action,stop}.md) so even a quiet tick shows "branch conflicts with main."
Escalation (when has_conflicts == true)
- Post a top-level bot note (or
AskUserQuestionin interactive runs) naming the conflicting file set; do not push. - Mirror the existing
escalate:/ ambiguous-blocking:philosophy: stop, summarize, hand to the human. - Skip when the watcher has already self-terminated on
approvals_left == 0.
⛔ Why not autonomous resolution
Conflict resolution is a higher risk class than anything the skill does today (emoji react, threaded reply, single scoped fix):
- Semantic merge, not mechanical. The real hazard is the files that do not show conflict markers — a struct both branches edited that auto-merges cleanly but silently drops a change, still compiling and passing tests. Needs judgment, not a rule. On !400 (merged),
config.gowas exactly this: a sharedConfigstruct that auto-merged, safe only because main's jobs/redis work was proto-only — which required manual verification to confirm. - Resets approvals. Any resolution pushes commits, which revokes approvals. An auto-resolver firing on every
mainadvance would repeatedly invalidate reviews — the opposite of the skill's "keep the MR signal legible" goal. - Generated files need regeneration, not merge. e.g.
*.pb.gomust be produced bybuf generate, not merge-resolved. A generic resolver does not know the per-file regen command.
If autonomous resolution is ever added, gate it behind a tightly-scoped allowlist (a generated file with a declared regen command; a lockfile with a documented strategy). Anything touching hand-written logic → escalate.
🛠️ Implementation pointers
- Per-iteration flow: add the conflict check after the exit-gate step in
.claude/skills/mr-watch/SKILL.md. - Templates: add a
Conflictsrow to.claude/skills/mr-watch/assets/reports/{quiet,action,stop}.md. - Commit prefix:
chore(skills):(skills are tooling, not product features). - Branch from current
main: the skill'srequest-rereview.shrecently took a fix there (6508940).
✅ Acceptance criteria
- Each iteration reads
has_conflicts/detailed_merge_statusand reports it. - A
Conflictsrow is present in all three report templates. -
has_conflicts == trueroutes to escalation (bot note /AskUserQuestionnaming the conflicting files), with no push. - Escalation is skipped after self-termination on
approvals_left == 0. - No autonomous resolution is added — or, if a scoped allowlist class is added, it is documented and tested.