Skip to content

Conversation

@ThomasK33
Copy link
Member

Summary

Show the exact commits applied (or that would be applied) when using the task_apply_git_patch tool, instead of only showing a commit count + HEAD SHA.

Background

The Apply Patch tool UI currently only surfaced a summary count. For auditing / review it’s helpful to render the per-commit subjects (and SHAs for real applies) so users can see precisely what changed.

Implementation

  • Tool result schema now returns appliedCommits: { subject, sha? }[] (and drops the redundant appliedCommitCount).
  • Backend captures HEAD before git am, then enumerates newly-created commits via git log <before>..HEAD.
    • Dry-run: returns subjects only (no SHA).
    • Real apply: returns {sha, subject} and headCommitSha.
  • UI renders a new Commits section and derives the count from appliedCommits.length.
    • Includes a legacy fallback for old history that only has appliedCommitCount.

Validation

  • make static-check

Risks

Low. This is additive UI + tool output plumbing; the git apply behavior is unchanged. Commit listing is best-effort and falls back safely.


📋 Implementation Plan

Show applied commits in Apply Patch tool UI

Context / Why

The Apply Patch tool UI currently shows:

  • the source task ID,
  • options (dry_run, three_way, force),
  • a summary commit count, and
  • the resulting HEAD SHA.

For review/auditing it’s useful to show exactly which commits were (or would be) applied. If we always return a commit list, the UI can derive the count from appliedCommits.length (so we can drop the redundant appliedCommitCount field).

Evidence (repo pointers)

  • UI: src/browser/components/tools/TaskApplyGitPatchToolCall.tsx currently renders commit count + HEAD, but no commit list.
  • Schema: TaskApplyGitPatchToolResultSchema in src/common/utils/tools/toolDefinitions.ts currently requires appliedCommitCount.
  • Backend: src/node/services/tools/task_apply_git_patch.ts runs git am and (for real applies) already reads HEAD after success; it can capture HEAD before the apply and run git log to enumerate commits.
  • Dry-run uses a temp worktree (git worktree add --detach ... HEAD); we can compute the would-apply commit subjects there too.

Plan (recommended approach)

1) Update tool result schema: required appliedCommits, remove appliedCommitCount

File: src/common/utils/tools/toolDefinitions.ts

Define a commit entry where subject is always present and sha is optional (so dry-runs can omit unstable SHAs):

const AppliedCommitSchema = z
  .object({
    subject: z.string().min(1),
    sha: z.string().min(1).optional(),
  })
  .strict();

export const TaskApplyGitPatchToolResultSchema = z.union([
  z
    .object({
      success: z.literal(true),
      taskId: z.string(),
      appliedCommits: z.array(AppliedCommitSchema),
      headCommitSha: z.string().optional(),
      dryRun: z.boolean().optional(),
      note: z.string().optional(),
    })
    .strict(),
  z
    .object({
      success: z.literal(false),
      taskId: z.string(),
      error: z.string(),
      note: z.string().optional(),
    })
    .strict(),
]);

Net LoC: ~15–30

2) Backend: always populate appliedCommits

File: src/node/services/tools/task_apply_git_patch.ts

Goals:

  • Success results always include appliedCommits.
  • Real applies include { sha, subject }.
  • Dry-runs include { subject } only (no sha), since the commit IDs can differ when applying “for real” later.
  • Never fail the tool call solely because commit-listing failed (fall back to best-effort subjects).

Implementation sketch:

async function getAppliedCommits(params: {
  cwd: string;
  beforeHeadSha: string | undefined;
  commitCountHint: number | undefined;
  includeSha: boolean;
}): Promise<Array<{ sha?: string; subject: string }>> {
  // Prefer range-based log when beforeHeadSha is available.
  // Fallback to -n <commitCountHint> on HEAD.
  // If all else fails, return [].
}

// real apply:
const beforeHeadSha = await tryRevParse("HEAD");
await gitAm(...);
const afterHeadSha = await tryRevParse("HEAD");
const appliedCommits = await getAppliedCommits({
  cwd,
  beforeHeadSha,
  commitCountHint: artifact.commitCount,
  includeSha: true,
});

// dry-run apply (inside temp worktree):
const beforeHeadSha = await tryRevParse("HEAD", { cwd: dryRunWorktreePath });
await gitAm(..., { cwd: dryRunWorktreePath });
const appliedCommits = await getAppliedCommits({
  cwd: dryRunWorktreePath,
  beforeHeadSha,
  commitCountHint: artifact.commitCount,
  includeSha: false,
});

Recommended git command for listing:

  • With range:
    • git log --reverse --format=%H%x00%s <before>..HEAD
  • With count fallback:
    • git log -n <count> --reverse --format=%H%x00%s HEAD

Parsing:

  • Split by \n, then split each line by \0.
  • Use assert only after checking exitCode === 0 (delimiter should always exist in that case).

Net LoC: ~60–120

3) UI: derive count from appliedCommits.length and render the list

File: src/browser/components/tools/TaskApplyGitPatchToolCall.tsx

Changes:

  • Replace all successResult.appliedCommitCount usage with successResult.appliedCommits.length.
  • Add a “Commits” section that renders subjects, and renders a copyable SHA only when sha exists.
  • Be defensive for legacy stored tool outputs:
    • If appliedCommits is missing (old history), fall back to showing appliedCommitCount if present and omit the list.

Net LoC: ~30–60

4) Update stories + tests

  • Update Storybook mocks:
    • src/browser/stories/mockFactory.ts
    • src/browser/stories/App.task.stories.tsx
      to populate appliedCommits instead of appliedCommitCount.
  • Add/update a UI test to assert the commits section renders and shows subjects (and SHA when present).

Net LoC: ~40–100


Alternative: keep appliedCommitCount as optional

If you want extra resilience (e.g., if git log fails and we only have subjects), we could keep appliedCommitCount?: number as an optional field.

Pros:

  • Lets the UI show an accurate count even if commit listing fails.

Cons:

  • Redundant with appliedCommits.length when listing succeeds.

Generated with mux • Model: openai:gpt-5.2 • Thinking: xhigh • Cost: $3.43

@github-actions github-actions bot added the enhancement New feature or functionality label Feb 2, 2026
Ensure Apply Patch tool stories expand the card reliably in test-storybook by targeting the specific tool header and waiting for the expanded state.

---

_Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: $19.93_

<!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=19.93 -->
@ThomasK33 ThomasK33 force-pushed the feat/task-apply-git-patch-commits branch from 63f7844 to ede451f Compare February 3, 2026 09:51
CI flakes showed the commit subject strings sometimes weren't found with a strict findByText() match.

Switch these plays to assert against messageWindow.textContent (substring match) after expansion, which is resilient to text being split across nodes or embedded in a larger text block. Keep the per-assertion timeout under the test-runner per-story timeout.

---

_Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: $20.58_

<!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=20.58 -->
@ThomasK33 ThomasK33 added this pull request to the merge queue Feb 3, 2026
github-merge-queue bot pushed a commit that referenced this pull request Feb 3, 2026
## Summary
Show the exact commits applied (or that would be applied) when using the
`task_apply_git_patch` tool, instead of only showing a commit count +
HEAD SHA.

## Background
The Apply Patch tool UI currently only surfaced a summary count. For
auditing / review it’s helpful to render the per-commit subjects (and
SHAs for real applies) so users can see precisely what changed.

## Implementation
- Tool result schema now returns `appliedCommits: { subject, sha? }[]`
(and drops the redundant `appliedCommitCount`).
- Backend captures `HEAD` before `git am`, then enumerates newly-created
commits via `git log <before>..HEAD`.
  - Dry-run: returns subjects only (no SHA).
  - Real apply: returns `{sha, subject}` and `headCommitSha`.
- UI renders a new **Commits** section and derives the count from
`appliedCommits.length`.
- Includes a legacy fallback for old history that only has
`appliedCommitCount`.

## Validation
- `make static-check`

## Risks
Low. This is additive UI + tool output plumbing; the git apply behavior
is unchanged. Commit listing is best-effort and falls back safely.

---

<details>
<summary>📋 Implementation Plan</summary>

# Show applied commits in Apply Patch tool UI

## Context / Why
The **Apply Patch** tool UI currently shows:
- the source task ID,
- options (`dry_run`, `three_way`, `force`),
- a summary commit count, and
- the resulting `HEAD` SHA.

For review/auditing it’s useful to show **exactly which commits were (or
would be) applied**. If we always return a commit list, the UI can
derive the count from `appliedCommits.length` (so we can drop the
redundant `appliedCommitCount` field).

## Evidence (repo pointers)
- UI: `src/browser/components/tools/TaskApplyGitPatchToolCall.tsx`
currently renders commit count + `HEAD`, but no commit list.
- Schema: `TaskApplyGitPatchToolResultSchema` in
`src/common/utils/tools/toolDefinitions.ts` currently requires
`appliedCommitCount`.
- Backend: `src/node/services/tools/task_apply_git_patch.ts` runs `git
am` and (for real applies) already reads `HEAD` after success; it can
capture `HEAD` before the apply and run `git log` to enumerate commits.
- Dry-run uses a temp worktree (`git worktree add --detach ... HEAD`);
we can compute the would-apply commit subjects there too.

## Plan (recommended approach)

### 1) Update tool result schema: required `appliedCommits`, remove
`appliedCommitCount`
**File:** `src/common/utils/tools/toolDefinitions.ts`

Define a commit entry where `subject` is always present and `sha` is
optional (so dry-runs can omit unstable SHAs):

```ts
const AppliedCommitSchema = z
  .object({
    subject: z.string().min(1),
    sha: z.string().min(1).optional(),
  })
  .strict();

export const TaskApplyGitPatchToolResultSchema = z.union([
  z
    .object({
      success: z.literal(true),
      taskId: z.string(),
      appliedCommits: z.array(AppliedCommitSchema),
      headCommitSha: z.string().optional(),
      dryRun: z.boolean().optional(),
      note: z.string().optional(),
    })
    .strict(),
  z
    .object({
      success: z.literal(false),
      taskId: z.string(),
      error: z.string(),
      note: z.string().optional(),
    })
    .strict(),
]);
```

**Net LoC:** ~15–30

### 2) Backend: always populate `appliedCommits`
**File:** `src/node/services/tools/task_apply_git_patch.ts`

Goals:
- Success results always include `appliedCommits`.
- Real applies include `{ sha, subject }`.
- Dry-runs include `{ subject }` only (no sha), since the commit IDs can
differ when applying “for real” later.
- Never fail the tool call solely because commit-listing failed (fall
back to best-effort subjects).

Implementation sketch:

```ts
async function getAppliedCommits(params: {
  cwd: string;
  beforeHeadSha: string | undefined;
  commitCountHint: number | undefined;
  includeSha: boolean;
}): Promise<Array<{ sha?: string; subject: string }>> {
  // Prefer range-based log when beforeHeadSha is available.
  // Fallback to -n <commitCountHint> on HEAD.
  // If all else fails, return [].
}

// real apply:
const beforeHeadSha = await tryRevParse("HEAD");
await gitAm(...);
const afterHeadSha = await tryRevParse("HEAD");
const appliedCommits = await getAppliedCommits({
  cwd,
  beforeHeadSha,
  commitCountHint: artifact.commitCount,
  includeSha: true,
});

// dry-run apply (inside temp worktree):
const beforeHeadSha = await tryRevParse("HEAD", { cwd: dryRunWorktreePath });
await gitAm(..., { cwd: dryRunWorktreePath });
const appliedCommits = await getAppliedCommits({
  cwd: dryRunWorktreePath,
  beforeHeadSha,
  commitCountHint: artifact.commitCount,
  includeSha: false,
});
```

Recommended git command for listing:
- With range:
  - `git log --reverse --format=%H%x00%s <before>..HEAD`
- With count fallback:
  - `git log -n <count> --reverse --format=%H%x00%s HEAD`

Parsing:
- Split by `\n`, then split each line by `\0`.
- Use `assert` only after checking `exitCode === 0` (delimiter should
always exist in that case).

**Net LoC:** ~60–120

### 3) UI: derive count from `appliedCommits.length` and render the list
**File:** `src/browser/components/tools/TaskApplyGitPatchToolCall.tsx`

Changes:
- Replace all `successResult.appliedCommitCount` usage with
`successResult.appliedCommits.length`.
- Add a “Commits” section that renders subjects, and renders a copyable
SHA only when `sha` exists.
- Be defensive for legacy stored tool outputs:
- If `appliedCommits` is missing (old history), fall back to showing
`appliedCommitCount` if present and omit the list.

**Net LoC:** ~30–60

### 4) Update stories + tests
- Update Storybook mocks:
  - `src/browser/stories/mockFactory.ts`
  - `src/browser/stories/App.task.stories.tsx`
  to populate `appliedCommits` instead of `appliedCommitCount`.
- Add/update a UI test to assert the commits section renders and shows
subjects (and SHA when present).

**Net LoC:** ~40–100

---

<details>
<summary>Alternative: keep <code>appliedCommitCount</code> as
optional</summary>

If you want extra resilience (e.g., if `git log` fails and we only have
subjects), we could keep `appliedCommitCount?: number` as an optional
field.

Pros:
- Lets the UI show an accurate count even if commit listing fails.

Cons:
- Redundant with `appliedCommits.length` when listing succeeds.

</details>

</details>

---

_Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh` •
Cost: `$3.43`_

<!-- mux-attribution: model=openai:gpt-5.2 thinking=xhigh costs=3.43 -->
Merged via the queue into main with commit 8ef9070 Feb 3, 2026
23 checks passed
@ThomasK33 ThomasK33 deleted the feat/task-apply-git-patch-commits branch February 3, 2026 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant