# How Diff Generation and Merge Readiness Checking Work in Open Agents

> Discover how Open Agents creates file diffs and checks merge readiness with efficient, stateful polling that respects GitHub API limits. Learn the inner workings of this powerful tool for seamless integration.

- Repository: [Vercel Labs/open-agents](https://github.com/vercel-labs/open-agents)
- Tags: deep-dive
- Published: 2026-04-16

---

**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`](https://github.com/vercel-labs/open-agents/blob/main/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:

```ts
// 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):

```ts
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 `+`:

```ts
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`](https://github.com/vercel-labs/open-agents/blob/main/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`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/merge-readiness-polling.ts), which defines the `MergeReadinessPollingState` type and critical constants:

```ts
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 mergeability
- `reasons`: Array of blocking reason strings
- `checks`: Object with `requiredTotal` and `pending` counts
- `checkRuns`: Array of GitHub check run data

### The Decision Algorithm

The `shouldPollMergeReadiness` function implements a cautious polling strategy to avoid GitHub API rate limits:

```ts
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

```ts
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

```ts
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

```tsx
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.ts`](https://github.com/vercel-labs/open-agents/blob/main/packages/shared/lib/diff.ts) through `createUnifiedDiff` and `createEditDiffLines`, handling line-splitting, collapse thresholds (15 lines), and syntax highlighting.
- **Merge readiness** logic lives in [`apps/web/lib/merge-readiness-polling.ts`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/merge-readiness-polling.ts), where `shouldPollMergeReadiness` implements intelligent backoff based on transient vs. permanent blocking states.
- **Polling respects limits** via `MERGE_READINESS_EMPTY_CHECKS_MAX_POLLS` (6 attempts) and `MERGE_READINESS_POLL_INTERVAL_MS` (5 seconds), preventing API abuse.
- **UI integration** uses `useSessionDiff` to 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`](https://github.com/vercel-labs/open-agents/blob/main/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`](https://github.com/vercel-labs/open-agents/blob/main/apps/web/lib/merge-readiness-polling.ts), the actual GitHub API calls typically reside in [`apps/web/lib/github/client.ts`](https://github.com/vercel-labs/open-agents/blob/main/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.