How Diff Generation and Merge Readiness Checking Work in Open Agents
Open Agents generates file-level diffs using shared utility functions and determines merge readiness through a stateful polling mechanism that respects GitHub API limits.
The vercel-labs/open-agents repository separates concerns between diff generation and merge readiness checking across its monorepo structure. Understanding how diff generation and merge readiness checking work reveals why the codebase splits these responsibilities between shared utilities and web-specific polling logic.
Diff Generation Architecture
Open Agents produces human-readable diffs on the shared package side while consuming them through React hooks on the web side. This separation ensures consistent diff formatting across both server and client contexts.
Core Utilities in the Shared Package
The heart of diff generation lives in packages/shared/lib/diff.ts. This file exports several key functions that transform raw string content into structured diff representations.
Line normalization begins with splitLines, which handles edge cases in file content:
// packages/shared/lib/diff.ts
export function splitLines(content: string): string[] {
// Removes trailing empty line when file ends with newline
const lines = content.split('\n');
if (lines[lines.length - 1] === '') {
lines.pop();
}
return lines;
}
Building Edit Diff Lines
The createEditDiffLines function constructs an array of DiffLine objects representing additions, removals, and separators. To prevent UI overflow, the implementation collapses large diffs when they exceed DIFF_MAX_EDIT_LINES (15 lines):
export function createEditDiffLines(
oldString: string,
newString: string,
startLine = 1,
maxLines = DIFF_MAX_EDIT_LINES,
) {
// Returns DiffLine[] with type: 'addition' | 'removal' | 'separator'
// Collapses middle section with '...' separator if > maxLines
}
Unified Diff Formatting
For Git-compatible output, createUnifiedDiff generates classic unified diff headers (--- a/file, +++ b/file, @@ -l,r +l,r @@) and prefixes old lines with - and new lines with +:
export function createUnifiedDiff(
oldString: string,
newString: string,
filePath: string,
startLine = 1,
): UnifiedDiffResult {
// Returns { diff: string, additions: number, removals: number }
}
Syntax highlighting for new files is handled by createNewFileCodeLines, which detects language from file extensions via getLanguageFromPath and optionally applies a highlighter function.
React Hook Integration
The web application consumes these diffs through apps/web/hooks/use-session-diff.ts. This SWR-based hook retrieves diffs either from a live sandbox connection or from cached snapshots when offline, returning a consistent payload to UI components.
Merge Readiness Polling Logic
While diffs show what changed, merge readiness checking determines when those changes can safely enter the main branch. This logic resides entirely in the web package.
State Definition and Constants
The polling mechanism centers on apps/web/lib/merge-readiness-polling.ts, which defines the MergeReadinessPollingState type and critical constants:
const TRANSIENT_MERGE_READINESS_REASONS = new Set([
"GitHub is still calculating mergeability",
"Required checks are still pending",
"Required checks are still in progress",
"Branch protection requirements are not yet satisfied",
]);
export const MERGE_READINESS_POLL_INTERVAL_MS = 5_000;
export const MERGE_READINESS_EMPTY_CHECKS_MAX_POLLS = 6;
The state structure tracks:
canMerge: Boolean indicating current mergeabilityreasons: Array of blocking reason stringschecks: Object withrequiredTotalandpendingcountscheckRuns: Array of GitHub check run data
The Decision Algorithm
The shouldPollMergeReadiness function implements a cautious polling strategy to avoid GitHub API rate limits:
export function shouldPollMergeReadiness(params: {
readiness: MergeReadinessPollingState | null;
emptyChecksPollCount: number;
}): boolean {
const { readiness, emptyChecksPollCount } = params;
// No PR exists → stop polling
if (!readiness?.pr) return false;
// Still pending required checks → keep polling
if (readiness.checks.pending > 0) return true;
// PR already mergeable → stop polling
if (readiness.canMerge) return false;
// Permanent blocks or exhausted retries → stop polling
if (
readiness.checks.requiredTotal > 0 ||
readiness.checkRuns.length > 0 ||
emptyChecksPollCount >= MERGE_READINESS_EMPTY_CHECKS_MAX_POLLS
) {
return false;
}
// Only continue if blocked by transient reasons
return readiness.reasons.some((r) =>
TRANSIENT_MERGE_READINESS_REASONS.has(r),
);
}
This function returns true only while transient merge-readiness reasons are present and no required checks are pending, no successful merge occurred, and the PR still exists.
Practical Implementation Examples
Generating a Unified Diff
import { createUnifiedDiff } from "@/packages/shared/lib/diff";
const oldContent = "console.log('old');\n";
const newContent = "console.log('new');\n";
const { diff, additions, removals } = createUnifiedDiff(
oldContent,
newContent,
"src/app/example.ts",
);
console.log(diff);
/*
--- a/src/app/example.ts
+++ b/src/app/example.ts
@@ -1,1 +1,1 @@
-console.log('old');
+console.log('new');
*/
Implementing Merge Readiness Polling
import { shouldPollMergeReadiness, MERGE_READINESS_POLL_INTERVAL_MS }
from "@/apps/web/lib/merge-readiness-polling";
let pollCount = 0;
const state = {
canMerge: false,
reasons: ["GitHub is still calculating mergeability"],
pr: { number: 42 },
checkRuns: [],
checks: { requiredTotal: 0, pending: 0 },
};
while (shouldPollMergeReadiness({
readiness: state,
emptyChecksPollCount: pollCount
})) {
await new Promise(r => setTimeout(r, MERGE_READINESS_POLL_INTERVAL_MS));
pollCount++;
// Re-fetch fresh state from GitHub API...
}
Consuming Diffs in React Components
import { useSessionDiff } from "@/apps/web/hooks/use-session-diff";
function DiffViewer({ sessionId }: { sessionId: string }) {
const { diff, isLoading, error, isStale } = useSessionDiff(
sessionId,
true // sandboxConnected
);
if (isLoading) return <p>Loading diff…</p>;
if (error) return <p>Error: {error}</p>;
return (
<pre>
{diff?.diff ?? (isStale ? "Showing cached diff" : "No diff available")}
</pre>
);
}
Summary
- Diff generation occurs in
packages/shared/lib/diff.tsthroughcreateUnifiedDiffandcreateEditDiffLines, handling line-splitting, collapse thresholds (15 lines), and syntax highlighting. - Merge readiness logic lives in
apps/web/lib/merge-readiness-polling.ts, whereshouldPollMergeReadinessimplements intelligent backoff based on transient vs. permanent blocking states. - Polling respects limits via
MERGE_READINESS_EMPTY_CHECKS_MAX_POLLS(6 attempts) andMERGE_READINESS_POLL_INTERVAL_MS(5 seconds), preventing API abuse. - UI integration uses
useSessionDiffto bridge sandbox-generated diffs with React components, supporting both live and cached modes.
Frequently Asked Questions
What limits the size of displayed diffs in Open Agents?
The createEditDiffLines function enforces a DIFF_MAX_EDIT_LINES constant of 15 lines. When changes exceed this threshold, the function inserts a separator line (...) to collapse the middle section, keeping the UI responsive while still indicating that content exists between the visible head and tail of the diff.
How does Open Agents know when to stop polling for merge status?
The shouldPollMergeReadiness function stops polling when: the PR becomes mergeable (canMerge: true), required checks are defined but not pending, the PR ceases to exist, or polling hits the empty-checks limit of 6 attempts. It only continues polling while GitHub reports transient states like "still calculating mergeability" with no defined required checks.
What diff format does Open Agents generate?
According to packages/shared/lib/diff.ts, Open Agents produces unified diff format compatible with Git tooling. The createUnifiedDiff function generates standard headers (--- a/..., +++ b/..., @@ ... @@) and prefixes removed lines with - and added lines with +, returning both the string and metadata counts (additions, removals).
Where does merge readiness data originate?
While the decision logic lives in apps/web/lib/merge-readiness-polling.ts, the actual GitHub API calls typically reside in apps/web/lib/github/client.ts (referenced but not detailed in the core logic files). This client populates the MergeReadinessPollingState object that shouldPollMergeReadiness evaluates to determine polling continuation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →