fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(pr-followup): detect merge conflicts in scheduled sync - #600

Merged
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts
Jul 9, 2026
Merged

fix(pr-followup): detect merge conflicts in scheduled sync#600
joryirving merged 1 commit into
mainfrom
fix/sync-detect-merge-conflicts

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

Change

  • github.ts: add fetchPullRequestMergeState(repo, pr) — a per-PR detail GET returning mergeable_state/mergeable (tolerates transient failure).
  • sync/route.ts: call it per open bot PR (concurrently with comments/reviews/checks) and set pr.mergeable so the existing conflict ingestion gets its signal:
    • dirtyCONFLICTINGingestMergeConflict enqueues
    • clean/other → MERGEABLEclearResolvedConflictItems clears a resolved item
    • unknown/null → leave unset — GitHub is still computing; neither enqueue nor prematurely clear (resolves on a later sync)

Verification

  • npm run typecheck clean; eslint clean on changed files.
  • vitest sync route: 8 passed (2 new — dirty→CONFLICTING enqueue; unknown→no-op); github lib: 66 passed.

Notes

  • One extra per-PR GET per open bot PR per sync (every 15 min) — bounded by open-bot-PR count.
  • The webhook path already carried mergeable_state; this brings the pull-based sync (the active mechanism) to parity.

The conflict-fix pipeline (ingestMergeConflict -> MERGE_CONFLICT queue item ->
bridge rebase workload -> reconcile) was fully built but never fired: the sync
reads pr.mergeable from the PR *list* endpoint, which GitHub does not populate
(mergeability is computed only on the per-PR GET). So pr.mergeable was always
undefined and conflicting bot PRs (green + approved but behind main) sat forever
with no signal enqueued.
Add fetchPullRequestMergeState (per-PR detail GET) and call it per open bot PR
in the sync loop, mapping mergeable_state to the enum the existing conflict
ingestion consumes: dirty -> CONFLICTING (enqueue), clean/etc -> MERGEABLE
(clear a resolved item), unknown/null -> leave unset (GitHub still computing;
neither enqueue nor prematurely clear).
Tests: dirty PR -> ingestMergeConflict called with CONFLICTING; unknown ->
neither enqueue nor clear.

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a bug in the PR-followup scheduled sync where merge conflicts were silently undetected. The root cause: the GitHub PR list endpoint doesn't populate mergeable/mergeable_state, so the existing conflict detection branch never fired in production.

Change-by-Change Findings

src/lib/github.ts (+26 lines)

  • Added fetchPullRequestMergeState(repoFullName, prNumber) — a per-PR detail GET that returns mergeable_state and mergeable.
  • Transient failures degrade to { mergeableState: null, mergeable: null } with a silent catch, letting the next sync retry.
  • The function is used only by the sync route; not exported as a standalone route.

src/app/api/pr-followup/sync/route.ts (+17/-2 lines)

  • Added fetchPullRequestMergeState import.
  • Added mergeState to the existing Promise.all concurrent fetch alongside comments, reviews, and check-runs — one extra per-PR GET per open bot PR per sync (every ~15 min).
  • Maps mergeable_state to the conflict ingestion enum:
    • "dirty"pr.mergeable = "CONFLICTING" → triggers ingestMergeConflict
    • "clean"/other → pr.mergeable = "MERGEABLE" → triggers clearResolvedConflictItems
    • "unknown"/null → leaves pr.mergeable unset → neither enqueue nor premature clear; resolves on next sync.

src/app/api/pr-followup/sync/route.test.ts (+64 lines)

  • Two new test cases covering the bug fix scenarios.

Required Checks

✅ verify route access controls are in place

The route was already protected by authorizeRequest (bearer token via DISPATCH_AGENT_TOKEN per the codebase's auth model). The PR does not remove, weaken, or bypass any auth. It only adds a new server-side GitHub API call. The existing authentication pattern is preserved.

✅ check for unintended public endpoints

No new API routes were introduced. The PR modifies only the existing POST /api/pr-followup/sync endpoint. The new fetchPullRequestMergeState helper is a plain function called within the sync route, not a route handler. No new HTTP listeners or endpoints are exposed.

Standards Compliance

  • API routes return appropriate HTTP status codes: ✅ 200 on success; the existing error handling pattern is unchanged.
  • Error handling: ✅ fetchPullRequestMergeState uses a silent catch that degrades to null rather than throwing, consistent with the adjacent fetches that also .catch(() => []).
  • Validation: ✅ The mergeableState string is normalized with .toLowerCase() before comparison.
  • No commit of secrets: ✅ GITHUB_TOKEN is used from env; not logged or persisted.
  • Code style: ✅ Matches surrounding code; npm run typecheck and eslint pass.

Linked Issue Fit

No linked issues are present in the corpus. The PR body provides sufficient context: the merge-conflict pipeline was built and unit-tested but never fired because pr.mergeable was always undefined in the scheduled sync path. The fix brings the pull-based sync (active mechanism) to parity with the webhook path, which already carried mergeable_state.

Evidence Provider Findings

No evidence providers are configured for this PR.

Tool Harness Findings

The tool harness issued no tool calls; reviewed corpus directly.

CI Check Results

CheckState
Typecheck✅ success
Build✅ success
Lint✅ success
Tests✅ success
Docker Build✅ success

Unknowns / Needs Verification

None identified. The PR is fully test-covered and passes all CI gates.

@joryirving
joryirving merged commit 3f3d00b into mainJul 9, 2026
6 checks passed
@joryirving
joryirving deleted the fix/sync-detect-merge-conflicts branch July 9, 2026 14:20
Sign up for freeto 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

@joryirving