Skip to content

[WIP] Fix aw.patch generation logic to handle local commits#4409

Merged
pelikhan merged 4 commits intomainfrom
copilot/fix-aw-patch-generation-logic
Nov 20, 2025
Merged

[WIP] Fix aw.patch generation logic to handle local commits#4409
pelikhan merged 4 commits intomainfrom
copilot/fix-aw-patch-generation-logic

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Nov 20, 2025

Fix aw.patch generation for locally committed changes ✅

Problem Statement

The current patch generation logic only looks for explicitly named branches in the JSONL output. When an LLM makes commits directly to the currently checked out branch during action execution, these commits are not captured in the patch file, resulting in an empty patch.

Issue: https://github.com/githubnext/gh-aw/actions/runs/19544824714

Implementation ✅

  • Explore repository structure and understand patch generation flow
  • Identify the issue: generate_git_patch.sh only generates patches when it finds a branch name in JSONL output
  • Modify generate_git_patch.sh to detect commits made to current HEAD since checkout SHA
  • Add fallback logic: when no branch name found, check if commits exist since GITHUB_SHA
  • Add extensive logging throughout the patch generation process
  • Update tests to cover the new scenario
  • Build and test the changes
  • Verify the fix resolves the issue

Solution Details

Two-Strategy Approach

Strategy 1 (Existing): Branch-based patch generation

  • Looks for branch name in JSONL output from create_pull_request or push_to_pull_request_branch
  • Generates patch from merge-base or origin branch
  • Remains the primary strategy when branch names are available

Strategy 2 (NEW): HEAD-based patch generation

  • Activates when: No branch name found in JSONL output
  • Validates: Checks if GITHUB_SHA is an ancestor of current HEAD
  • Generates: Patch from GITHUB_SHA..HEAD when commits are detected
  • Safe: Refuses to generate patch if history has diverged (not a clean fast-forward)

Extensive Logging (as requested)

The script now logs:

  1. Environment Information

    • GITHUB_SHA (checkout point)
    • DEFAULT_BRANCH
    • Current HEAD SHA
    • Current branch name
  2. Git Status

    • Recent commits (last 10)
    • Current working tree status
  3. JSONL Processing

    • File path being read
    • Whether branch name was found
    • Extracted branch name (if any)
  4. Strategy Selection

    • Which strategy is being used
    • Why that strategy was chosen
  5. Patch Generation Details

    • Base reference being used
    • Number of commits to be included
    • Commit SHAs and messages
    • Diff statistics
    • Exact git format-patch command being executed
  6. Patch File Information

    • File size (KB)
    • Line count
    • Number of commits included
    • List of commit SHAs in the patch

Tests Added ✅

  1. TestGitPatchFromHEADCommits (200+ lines)

    • Creates a git repository with initial commit
    • Simulates LLM making 2 commits to HEAD
    • Verifies patch is generated from GITHUB_SHA..HEAD
    • Validates patch contains both commits and file changes
  2. TestGitPatchPrefersBranchOverHEAD (120+ lines)

    • Tests that Strategy 1 takes precedence over Strategy 2
    • Creates a named branch with commits
    • Provides branch name in JSONL output
    • Verifies Strategy 1 is used (branch-based)
  3. TestGitPatchNoCommits (80+ lines)

    • Tests scenario where HEAD == GITHUB_SHA (no new commits)
    • Verifies no patch file is created
    • Confirms appropriate logging messages

Test Results ✅

=== RUN   TestGitPatchFromHEADCommits
--- PASS: TestGitPatchFromHEADCommits (0.06s)
=== RUN   TestGitPatchPrefersBranchOverHEAD
--- PASS: TestGitPatchPrefersBranchOverHEAD (0.06s)
=== RUN   TestGitPatchNoCommits
--- PASS: TestGitPatchNoCommits (0.03s)

All existing tests also continue to pass.

Files Changed

  • Modified: pkg/workflow/sh/generate_git_patch.sh (+97 lines)

    • Added Strategy 2 for HEAD-based patch generation
    • Enhanced logging throughout the script
    • Better error handling and validation
  • Added: pkg/workflow/git_patch_head_test.go (397 lines)

    • Comprehensive test coverage for new functionality
    • Tests all three scenarios: HEAD-based, branch-based priority, no commits
  • Updated: All .github/workflows/*.lock.yml files (17 files)

    • Recompiled with new patch generation logic
    • Now include enhanced logging in generated workflows

Impact

This fix ensures that:

  1. LLM commits are captured: When an LLM makes commits directly to the checked-out branch, those commits will now be included in the patch
  2. Backwards compatible: Existing workflows using branch names continue to work as before
  3. Well-logged: Extensive diagnostic information helps debug any future issues
  4. Safe: Won't create patches from diverged or ambiguous git histories

The solution directly addresses the problem statement and includes the requested extensive logging.

Original prompt

The aw.patch generation JavaScript logic needs to handle the case where the LLM has already made commits locally to the branch checked out in the action.

Currently this leads to an empty patch but what really needs to happen is that the commits created in the branch from the action checkout sha are bundled.

See this run where the agent created a commit but it was not captured in the patch.

https://github.com/githubnext/gh-aw/actions/runs/19544824714

Add extensive logging.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

- Modified generate_git_patch.sh to detect commits made to current HEAD
- Added Strategy 2: Falls back to GITHUB_SHA..HEAD when no branch name found
- Enhanced logging throughout patch generation process
- Shows environment information, git status, and commit details
- Added comprehensive tests for HEAD-based patch generation
- All existing tests continue to pass

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot continue working

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 20, 2025

@copilot continue working

The implementation is complete and ready for review:

✅ All tests pass (including 3 new comprehensive tests)
✅ Build succeeds
✅ Strategy 2 (HEAD-based patch generation) implemented with extensive logging
✅ Backwards compatible with existing branch-based approach

The fix handles the case where LLMs make commits directly to HEAD without creating a named branch. Commits since GITHUB_SHA will now be captured in the patch file.

Commit: fc78876

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Nov 20, 2025

✅ Agentic Changeset Generator completed successfully.

@pelikhan pelikhan marked this pull request as ready for review November 20, 2025 19:26
Copilot AI review requested due to automatic review settings November 20, 2025 19:26
@pelikhan pelikhan merged commit 4a8c23f into main Nov 20, 2025
3 of 4 checks passed
@pelikhan pelikhan deleted the copilot/fix-aw-patch-generation-logic branch November 20, 2025 19:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a bug in patch generation where commits made directly to the checked-out HEAD (without creating a named branch) were not captured. The solution implements a two-strategy approach with fallback logic and comprehensive diagnostic logging.

Key changes:

  • Added Strategy 2 (HEAD-based patch generation) as a fallback when no branch name is found in JSONL output
  • Enhanced diagnostic logging throughout the patch generation process (environment info, git status, strategy selection, etc.)
  • Added comprehensive test coverage for the new HEAD-based patch generation scenario

Reviewed Changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.

File Description
pkg/workflow/sh/generate_git_patch.sh Core fix: Added Strategy 2 for HEAD-based patch generation with fallback logic, enhanced logging, and validation of git history (merge-base ancestor check)
pkg/workflow/git_patch_head_test.go New test file with 3 comprehensive tests covering HEAD-based patches, strategy priority, and no-commits scenarios
.changeset/patch-fix-patch-generation-local-commits.md Changeset documenting the patch-level fix
.github/workflows/*.lock.yml (17 files) Recompiled workflow lock files incorporating the updated patch generation script
Comments suppressed due to low confidence (1)

pkg/workflow/sh/generate_git_patch.sh:217

  • Potential logic inconsistency: The script checks if /tmp/gh-aw/aw.patch exists (line 184) to display patch info, but this could show information even when PATCH_GENERATED=false. This can happen if:
  1. A previous patch file exists from a prior run
  2. The git format-patch command fails and writes an error message to the file

The patch info display should respect the PATCH_GENERATED flag to avoid showing misleading information. Consider:

if [ "$PATCH_GENERATED" = true ] && [ -f /tmp/gh-aw/aw.patch ]; then

This ensures patch info is only displayed when a patch was actually generated in this run.

if [ -f /tmp/gh-aw/aw.patch ]; then
  echo ""
  echo "=== Diagnostic: Patch file information ==="
  ls -lh /tmp/gh-aw/aw.patch
  
  # Get patch file size in KB
  PATCH_SIZE="$(du -k /tmp/gh-aw/aw.patch | cut -f1)"
  echo "Patch file size: ${PATCH_SIZE} KB"
  
  # Count lines in patch
  PATCH_LINES="$(wc -l < /tmp/gh-aw/aw.patch)"
  echo "Patch file lines: $PATCH_LINES"
  
  # Extract and count commits from patch file (each commit starts with "From <sha>")
  PATCH_COMMITS="$(grep -c "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch 2>/dev/null || echo "0")"
  echo "Commits included in patch: $PATCH_COMMITS"
  
  # List commit SHAs in the patch
  if [ "$PATCH_COMMITS" -gt 0 ]; then
    echo "Commit SHAs in patch:"
    grep "^From [0-9a-f]\{40\}" /tmp/gh-aw/aw.patch | sed 's/^From \([0-9a-f]\{40\}\).*/  \1/' || echo "Failed to extract commit SHAs"
  fi
  
  # Show the first 50 lines of the patch for review
  {
    echo '## Git Patch'
    echo ''
    echo '```diff'
    head -500 /tmp/gh-aw/aw.patch || echo "Could not display patch contents"
    echo '...'
    echo '```'
    echo ''
  } >> "$GITHUB_STEP_SUMMARY"
fi

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +351 to +360
cmd.Run()

cmd = exec.Command("git", "commit", "-m", "Initial")
cmd.Dir = tmpDir
cmd.Run()

// Get SHA
cmd = exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = tmpDir
output, _ := cmd.CombinedOutput()
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

Error handling is inconsistent in TestGitPatchNoCommits. The test ignores errors from git operations (lines 339, 343, 351, 355, 360), which could lead to false positives if git commands fail. Unlike the other two tests that properly check errors with t.Fatalf, this test silently continues even if setup operations fail.

Consider adding proper error checks:

if err := cmd.Run(); err != nil {
    t.Fatalf("Failed to configure git: %v", err)
}
Suggested change
cmd.Run()
cmd = exec.Command("git", "commit", "-m", "Initial")
cmd.Dir = tmpDir
cmd.Run()
// Get SHA
cmd = exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = tmpDir
output, _ := cmd.CombinedOutput()
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to add file to git: %v", err)
}
cmd = exec.Command("git", "commit", "-m", "Initial")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to commit file to git: %v", err)
}
// Get SHA
cmd = exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = tmpDir
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("Failed to get git SHA: %v, output: %s", err, output)
}

Copilot uses AI. Check for mistakes.

// Create empty safe-outputs
safeOutputsFile := filepath.Join(tmpDir, "safe-outputs.jsonl")
os.WriteFile(safeOutputsFile, []byte(""), 0644)
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

Missing error check for os.WriteFile operations. If these operations fail (e.g., due to permissions or disk space issues), the test will continue with an invalid test setup, potentially leading to confusing test failures or false positives.

Add error checks:

if err := os.WriteFile(testFile, []byte("content\n"), 0644); err != nil {
    t.Fatalf("Failed to write test file: %v", err)
}
Suggested change
os.WriteFile(safeOutputsFile, []byte(""), 0644)
if err := os.WriteFile(safeOutputsFile, []byte(""), 0644); err != nil {
t.Fatalf("Failed to write safe-outputs.jsonl: %v", err)
}

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +162
git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch
echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)"
PATCH_GENERATED=true
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The error message on line 160 may be misleading when the git format-patch command succeeds but writes the error message due to the || operator. The command git format-patch ... || echo "Failed to generate patch from HEAD" > ... will overwrite the patch file with the error message if format-patch returns a non-zero exit code, but the subsequent logging on line 161 will still report "Patch file created from commits on HEAD".

Consider checking the exit status before writing success messages:

if git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch; then
  echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)"
  PATCH_GENERATED=true
else
  echo "Failed to generate patch from HEAD"
fi
Suggested change
git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch
echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)"
PATCH_GENERATED=true
if git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch; then
echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)"
PATCH_GENERATED=true
else
echo "Failed to generate patch from HEAD"
fi

Copilot uses AI. Check for mistakes.
Comment on lines 108 to +110
git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch
echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)"
PATCH_GENERATED=true
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

Same issue as Strategy 2: The error handling pattern git format-patch ... || echo "Failed to generate patch from branch" > ... will overwrite a successful patch with an error message if format-patch returns non-zero, but line 109 will still log "Patch file created" and set PATCH_GENERATED=true regardless of whether the patch generation actually succeeded.

This is consistent with the existing behavior before this PR, but the same fix should be applied here for consistency:

if git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch; then
  echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)"
  PATCH_GENERATED=true
else
  echo "Failed to generate patch from branch"
fi
Suggested change
git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch
echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)"
PATCH_GENERATED=true
if git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch; then
echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)"
PATCH_GENERATED=true
else
echo "Failed to generate patch from branch"
fi

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants