Skip to content

create_pull_request patch uses stale origin/branchName instead of merge-base with default branch #28955

@mrjf

Description

@mrjf

Problem

In generate_git_patch.cjs, the full-mode patch generation (used by create_pull_request) uses a stale origin/branchName as the base ref when it exists locally, instead of computing the merge-base with the default branch. This causes the patch to include commits the agent never made.

Root Cause

File: actions/setup/js/generate_git_patch.cjs, lines 178-224 (Strategy 1, Full Mode)

// FULL MODE (for create_pull_request):
debugLog(`Strategy 1 (full): Checking if origin/${branchName} exists`);
try {
    execGitSync(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branchName}`], { cwd });
    baseRef = `origin/${branchName}`;   // ← BUG: uses stale remote-tracking ref
    debugLog(`Strategy 1 (full): Using existing origin/${branchName} as baseRef`);
} catch {
    // Falls through to merge-base with default branch — this path is CORRECT
    baseRef = execGitSync(["merge-base", "--", `origin/${defaultBranch}`, branchName], { cwd }).trim();
}

When origin/branchName exists locally, line 182 short-circuits to using it as the base ref. The patch is then:

git format-patch origin/branchName..branchName --stdout

The problem: origin/branchName is a stale local ref — it was fetched at the start of the workflow run (during the checkout step with fetch: ["*"]). It points to wherever the remote branch was at that moment, not the state of the branch before the agent made changes.

In the autoloop scenario:

  1. Workflow checks out the repo with fetch: ["*"], fetching all branches including origin/autoloop/tsb-perf-evolve (which is 50 commits behind main, 0 ahead — fully merged)
  2. Git credentials are cleaned (clean_git_credentials.sh)
  3. Agent runs, fast-forwards the local branch to origin/main, commits 1 file (src/core/series.ts), pushes
  4. Agent calls create_pull_request MCP tool
  5. MCP tool checks: does refs/remotes/origin/autoloop/tsb-perf-evolve exist? Yes — it was fetched in step 1
  6. baseRef = origin/autoloop/tsb-perf-evolve — pointing to the old commit (50 behind main)
  7. Patch = git format-patch old-commit..current-branch --stdout — includes all ~50 commits between the old branch position and current branch tip

Result: the patch contains .github/workflows/*, README.md, assets/tsessebe-logo.png, and other files from those 50 commits that the agent never touched. Only src/core/series.ts was the agent's actual change.

The Fix

The base ref should be the state of the branch before the agent made changes — i.e., the merge-base with the default branch. In full mode, always use the merge-base path (line 217) instead of short-circuiting to origin/branchName (line 182).

The fallback path already does the right thing:

baseRef = execGitSync(["merge-base", "--", `origin/${defaultBranch}`, branchName], { cwd }).trim();

This correctly computes the common ancestor between origin/main and the local branch, capturing exactly what the agent changed and nothing else.

The origin/branchName shortcut only makes sense for incremental mode (which already has its own code path at lines 141-172). In full mode it's always wrong when the remote branch is stale.

Impact

This bug blocks all three autoloop programs on githubnext/tsessebe:

Example failing run: https://github.com/githubnext/tsessebe/actions/runs/25053867836

Related

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions