Skip to content

feat(review): opt-in, comment-driven merge-conflict resolution#61

Merged
khaliqgant merged 2 commits into
mainfrom
feat/review-conflict-resolve
Jun 18, 2026
Merged

feat(review): opt-in, comment-driven merge-conflict resolution#61
khaliqgant merged 2 commits into
mainfrom
feat/review-conflict-resolve

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 14, 2026

Copy link
Copy Markdown
Member

What

Adds opt-in, comment-driven merge-conflict resolution to the review agent (pr-reviewer). Comment @relay fix conflicts on a PR and the agent resolves its merge conflicts.

The flow: cloud merges the base branch into the working tree → the harness resolves the conflict markers (preserving both sides' intent, never weakening tests or flipping safety defaults) → verifies the merged tree against the repo's CI command → cloud finalizes and pushes the merge commit. A conflict that needs human judgment is left in place and reported under ## Unresolved conflicts so a risky half-merge is never pushed.

This is distinct from cloud's existing deterministic conflictAutofix (clean rebase only; aborts on real textual conflicts).

Why

Requested: ability to comment at the review agent and have it fix merge conflicts, opt-in.

Changes (this repo)

  • review/persona.ts — declares capabilities.conflictResolve.
  • review/agent.tsissue_comment.created trigger; strict directive parser (matchesConflictDirective); commander authorization (isAuthorizedConflictCommander — never a bot; gated on PR author / APPROVERS / REVIEW_AUTHORS); readPr extended to read a PR from an issue_comment payload; resolveConflicts; conflictResolveHarnessPrompt.
  • review/README.md — documents the feature.
  • tests/review-agent.test.mjs — coverage for the matcher, commander auth, issue_comment readPr, and prompt invariants.

Dormant until cloud ships

This is inert until the cloud-side merge-in-tree + finalize-push flow lands. With no base-merge, the harness finds no markers and changes nothing.

Tracked in AgentWorkforce/cloud#2149.

Testing

npm test — typecheck clean, all 91 tests pass.

🤖 Generated with Claude Code


Summary by cubic

Add opt-in, comment-driven merge-conflict resolution to the review agent. Comment @relay fix conflicts on a PR to resolve merge markers, verify with CI, and have cloud finalize the merge; conflicts needing judgment are left for humans.

  • New Features - New features added
    • Triggered by issue_comment.created only on a strict @relay fix/resolve conflicts directive; casual mentions are ignored.
    • Authorization: only the PR author or users in APPROVERS/REVIEW_AUTHORS; never bots.
    • Hardened PR parsing for issue_comment: only treat comments on PRs (issue.pull_request present), prefer the PR opener for author gating, ignore plain issues.
    • Runs a conflict-resolution harness that preserves both sides’ intent, removes markers, verifies the merged tree CI-deep, and comments results; unresolved files are listed under “Unresolved conflicts”.
    • Declares conflictResolve in the persona; updates review/README.md; adds tests for directive matching, commander auth, PR parsing, and prompt invariants.
    • Inert until the cloud merge-in-tree + finalize-push flow ships; without it, no changes are made.

Written for commit 369e021. Summary will update on new commits.

Review in cubic

Comment "@relay fix conflicts" on a PR to have the review agent resolve
its merge conflicts. Cloud merges the base branch into the working tree,
the harness resolves the conflict markers (preserving both sides, never
weakening tests or flipping safety defaults), verifies the merged tree
CI-deep, and cloud finalizes and pushes the merge commit. Conflicts that
need human judgment are left in place under "## Unresolved conflicts" so
a risky half-merge is never pushed.

Distinct from cloud's deterministic conflictAutofix (clean rebase only).
Gated to authorized commenters (PR author / APPROVERS / REVIEW_AUTHORS,
never a bot) and the strict "@relay (fix|resolve) conflicts" directive.

Inert until cloud ships the merge-in-tree + finalize-push flow, tracked
in AgentWorkforce/cloud#2149: with no base-merge, the harness finds no
markers and changes nothing.

- review/persona.ts: declare capabilities.conflictResolve
- review/agent.ts: issue_comment.created trigger, directive parse,
  commander auth, readPr from issue_comment payloads, resolveConflicts,
  conflictResolveHarnessPrompt
- review/README.md + tests/review-agent.test.mjs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@khaliqgant, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 14 minutes and 26 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17e9c503-17eb-4c74-819e-a54da630426f

📥 Commits

Reviewing files that changed from the base of the PR and between 8c32a95 and 369e021.

📒 Files selected for processing (4)
  • review/README.md
  • review/agent.ts
  • review/persona.ts
  • tests/review-agent.test.mjs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/review-conflict-resolve

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@agent-relay-code

Copy link
Copy Markdown
Contributor

Findings

  • review/agent.ts:870 allows any non-bot GitHub user to trigger conflict resolution when APPROVERS and REVIEW_AUTHORS are unset. This contradicts the PR description/docs that say conflict resolution is limited to the PR author or trusted configured users, and it is risky because the conflict flow force-updates the PR branch after the harness resolves markers. This is semantic/safety behavior, so I did not edit it. Suggested direction: require commander === pr.author always, and additionally allow APPROVERS / REVIEW_AUTHORS members when those lists are configured.

Addressed comments

  • gemini-code-assist[bot]: quota-limit warning only, no code finding to validate or fix.
  • coderabbitai[bot]: review rate-limit warning only, no code finding to validate or fix.

Verification

  • Installed missing dependencies with npm install.
  • npm test passed: 90 tests.
  • npm run typecheck passed.
  • npm run compile passed.
  • Working tree is clean after verification.

PR metadata check showed the PR is open and mergeable, and the current head status returned CodeRabbit: success, but this should not be treated as ready while the authorization finding remains.

Reconcile #61 (opt-in comment-driven merge-conflict resolution) with #73
(VFS-based PR-state machinery):
- review/agent.ts: union github triggers (kept issues.labeled + slack from
  #73, added issue_comment.created from #61); kept #73's
  readPrReviewState/loadCheckSummary/loadReviews/rollupFromCheckSummary/
  deriveReviewDecision and #61's resolveConflicts/matchesConflictDirective/
  isAuthorizedConflictCommander; merged readPr to handle both issue_comment
  and issues.labeled payloads (single superset issue type).
- tests/review-agent.test.mjs: unioned imports so both PRs' cases coexist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@khaliqgant khaliqgant merged commit bbc807d into main Jun 18, 2026
2 checks passed
@khaliqgant khaliqgant deleted the feat/review-conflict-resolve branch June 18, 2026 15:02
@agent-relay-code

Copy link
Copy Markdown
Contributor

I left the working tree unchanged (no edits were needed). Here is my review.


PR #61 Review — feat(pr-reviewer): opt-in comment-driven merge-conflict resolution

Summary

This PR adds an opt-in, comment-triggered merge-conflict resolution path to the pr-reviewer agent. It introduces a new issue_comment.created trigger, a directive regex, an authorization gate, a harness prompt, and a conflictResolve persona capability. It also refactors readPr to read a PR from an issue_comment payload. Scope is tight and matches the stated PR purpose.

Verification (the way CI runs it)

  • npm run typecheck (tsc --noEmit) — clean.
  • npm test (tsc build + node --test tests/*.test.mjs) — 118 passed, 0 failed.
  • Installed dependencies first (node_modules was absent); the only CI workflow present (pullfrog.yml) is an agent runner, so the canonical gates are the package.json test/typecheck scripts.

Findings

No mechanical fixes were required — no lint, formatting, spelling, or import-order issues. The working tree is unchanged.

I validated the logic-sensitive areas and found them sound (no changes made — verification only):

  • readPr refactor (agent.ts:1263-1324) — Behavior is preserved for all pre-existing events. For payloads carrying pull_request, author/state/draft/labels resolution is byte-for-byte equivalent to the old code (pull_request.user.login ?? sender.login ?? 'unknown'). The new prIssue branch only activates when issue.pull_request != null, so check_run.completed and non-PR issues still resolve author to 'unknown' exactly as before. The label-source change is not a regression because shouldSkipReview prefers meta.labels and the merge-on-green path reads labels from VFS state, not from pr.labels.

  • Authorization gate isAuthorizedConflictCommander (agent.ts:262-269) — Correctly fail-closed: bot logins ([bot]) and empty commenters are always rejected (also breaking any fix-bot echo loop). The "unset trust lists → open" default intentionally mirrors the persona's existing reviewAuthorAllowlist convention, so it is not a new fail-open path.

  • Directive regex CONFLICT_DIRECTIVE_PATTERN (agent.ts:69) — Appropriately narrow. I verified it does not false-positive on @relayfile fix conflicts or @relayed …, and requires adjacent fix|resolve + conflicts?.

  • Event orderingissue_comment.created is excluded from mergeOnGreenEventType and the approval-merge check, so the comment flows straight to the conflict handler and reuses shouldSkipReview (respects draft/closed/label/author gates).

  • resolveConflicts (agent.ts:92-110) — Follows the same failReviewRun (Promise<never>, always throws) pattern as reviewAndFix; the trailing if (body) is redundant but harmless and consistent with existing code.

Addressed comments

  • No bot or human review comments were supplied to this run (.workforce/ contains only pr.diff, changed-files.txt, and context.json; context.json carries no review/comment metadata, and there were no inline threads to reconcile). Nothing to address.

Advisory Notes

  • None. The change stays within the PR's purpose; I found no unrelated cleanup to flag.

The PR is logic-complete, fully scoped, typechecks, and passes the full test suite locally. It depends on cloud-side merge-in-tree support (documented as inert until then), which is a human/product decision outside this harness. The remaining decision — whether to merge given that cloud-side dependency — requires human judgment.

@agent-relay-code

Copy link
Copy Markdown
Contributor

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

PR Review: factory state-name defaults + canary scheduling tooling

I reviewed the full diff (.workforce/pr.diff), traced its impact across callers/types/tests, and ran the canonical CI pipeline (npm cinpm run buildnpm testnpm pack --dry-run) end to end.

Summary of changes

  • src/config/schema.tslinear.states now defaults to a set of workflow-state names (DEFAULT_LINEAR_STATE_NAMES) instead of {}, so consumers needn't hand-configure them.
  • src/linear/state-resolver.ts — when a configured name can't be resolved via the catalog and there's no pinned UUID, optional roles (e.g. humanReview) now resolve to undefined instead of throwing; required roles still fail loudly.
  • scripts/factory-canary.sh + scripts/com.agentrelay.factory-canary.plist.example + README — scheduled sync-fidelity canary tooling wrapping the existing factory canary CLI command.
  • Two test files updated to match the new defaults.

Verification

  • Build: passes (tsc + tsc-alias, no type errors).
  • Tests: full suite passes (512/512) when stable. One test — factory.test.ts:1923 "keeps the live heartbeat fresh while draining a blocking live event burst" — flaked intermittently under full-suite parallel load (it passed in isolation and on a clean full re-run). It is a real-clock/vi.waitFor timing test in the heartbeat/lifecycle area, uses normal state.id issues, and is unrelated to this PR's changes. I did not touch it (lifecycle/heartbeat is safety-critical). Flagging as a pre-existing flake for human awareness, not a regression.
  • Pack: npm pack --dry-run succeeds.
  • Canary script: bash -n clean; I executed its generated JSON-parse + Slack-payload node snippets against fixture verdicts (valid verdict, and unparseable input) — both behave as documented.

Safety analysis of the resolver change (key item)

The new if (!REQUIRED_ROLES.includes(role)) return undefined is a behavior change but moves in the fail-safe direction:

  • It only affects optional roles (humanReview). With the new default name 'In Human Review', a catalog-less mount would previously throw at startup; now humanReview is left unresolved → hasHumanReview() is false → terminalState falls back to done. This is the documented degraded-but-functional contract, not a fail-open of a guard. Required roles still throw. No truthiness→presence swaps, no guard-default edits. This is correct and is the PR's stated purpose.

Findings

No blocking issues. No mechanical/lint fixes were needed (the diff is clean: imports, formatting, and spelling are all fine). I made no file edits — the working tree is unchanged (only the pre-existing untracked memory/ dir from the harness remains).

One minor observation (non-blocking, no change made): DEFAULT_LINEAR_STATE_NAMES is an untyped object literal duplicated in the .default(...) calls on lines 114 and 116 of schema.ts. It's correct as-is and the tests pin the exact shape; a satisfies/shared-const tidy would be cosmetic only and isn't required.

Addressed comments

  • No human reviews, approvals, or bot comments were present for this PR in the provided context (.workforce/context.json carries no review/comment data, and there is no review-comment artifact for this PR in the checkout). Nothing to address.

Advisory Notes

  • The intermittently-flaky heartbeat-burst test (factory.test.ts:1923) is unrelated to this PR but worth a separate hardening pass (e.g. de-flaking the real-clock vi.waitFor thresholds under load). It sits in lifecycle/heartbeat code, so it should be handled in its own human-authored change, not folded into this PR.

The PR is coherent, the change is fail-safe, and the full CI pipeline passes. The only non-green signal is a load-dependent flake in an unrelated lifecycle test, which is not introduced by this PR. Since a required check can flake red on CI (and I cannot confirm CI is green from here), I am not printing READY — a human should confirm the CI run is green (re-running the flaky test if needed) before merge.

@agent-relay-code

Copy link
Copy Markdown
Contributor

I have one observation worth noting about authorization logic. Let me re-verify the isAuthorizedConflictCommander fail-open behavior matches the existing persona convention before deciding whether it's a finding. The doc comment says "matching its other allowlists' unset → everyone". isAuthorizedApprover does the same: if (allow.length === 0) return true. So fail-open-when-unset is consistent with the existing approval path. This is an intentional, pre-existing design convention, not a regression introduced here. I'll note it as advisory only since the directive is gated by conflictResolve being inert until cloud support exists.

Let me do a final check: confirm no conflict between mergeOnGreenEventType and the new issue_comment.created path — an issue_comment is not in mergeOnGreenEventType, and the merge-on-green block runs before the issue_comment block at line 110-114. Since issue_comment.created returns false from mergeOnGreenEventType, it falls through correctly to line 126. Good.

The review is complete. Here are my findings.


Summary

PR #61 adds an opt-in, comment-driven merge-conflict resolution capability to the pr-reviewer agent. The change is well-scoped, internally consistent, and the diff matches the checked-out code exactly. Verification passed end-to-end:

  • npm run typecheck (tsc --noEmit) — clean
  • npm test (tsc compile + node --test tests/*.test.mjs) — 118/118 pass, including all 13 new/affected readPr/conflict tests
  • agentworkforce persona compile review/persona.ts — compiles; conflictResolve capability serializes; no committed artifact drift (persona.json is generated/untracked)

I made no file edits. No mechanical issues (lint/format/spelling/import-order) were found, and every substantive aspect either checks out or is appropriately gated.

What I verified about the changes

  • readPr refactor is the riskiest part (reorders payload extraction, adds issue-as-PR handling). Traced all callers: the merge-on-green path reads state authoritatively from readMergeOnGreenState/meta (not pr), and shouldSkipReview prefers meta over pr.labels, so the new label-flow change (labels now only flow from issue when issue.pull_request is present) is a correctness tightening, not a regression. The existing readPr resolves issue labeled payloads test still passes.
  • Fail-closed boundaries preserved: resolveConflicts calls failReviewRun (which is Promise<never> / throws) on empty output or non-zero exit — no silent success path. The conflict harness prompt keeps the no-git boundary, safety-default rules, test-protection rules, lifecycle/reaper prohibition, and the "## Unresolved conflicts" escape hatch that makes cloud abort rather than push a half-merge.
  • Authorization gating: bots are rejected ([bot] suffix), and the directive requires the explicit CONFLICT_DIRECTIVE_PATTERN, so a passing mention won't fire a force-update.
  • No lifecycle/termination/dispatch code touched.

Addressed comments

  • No bot or human review comments were present in .workforce/context.json or the workforce directory, and the PR carries no prior review threads to reconcile. Nothing to address.

Advisory Notes

  • isAuthorizedConflictCommander is fail-open when neither APPROVERS nor REVIEW_AUTHORS is set (return true). This is intentional and matches the existing isAuthorizedApprover convention ("unset → everyone"), and the doc comment calls it out. Since resolving conflicts force-updates the PR branch, operators relying on this in a public/forked context should set at least one allowlist. Left unchanged — flagging only so a human owns the trust-model decision; it is not a regression introduced by this PR.
  • The capability is inert until cloud implements the merge-in-tree + finalize-push flow (per the persona.ts comment and AgentWorkforce/cloud tracking). With no merge performed, the harness finds no markers and makes no change, so shipping this ahead of cloud support is safe.

The PR is mechanically clean, fully verified locally, and the only open item (fail-open authorization default) is an intentional design convention requiring human judgment rather than a defect. I cannot confirm GitHub's mergeable status or that all required CI checks have completed and passed from this sandbox, so I am not printing the ready sentinel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant