Skip to content

Commit f79392b

Browse files
Phlogistiqueclaude
andauthored
Fix e2e test scenario merge logic (#4)
* fix(tests): update e2e test to work with gh CLI v2.83.2 - Fix workflow run matching to use headBranch instead of unavailable eventPayload field - Reorganize conflict scenario to introduce conflicts in correct order - Update step numbering for clarity - Add GitHub API call to update PR branch before merging in conflict scenario The initial merge test now passes successfully. The conflict scenario test reveals an issue with updating branches that have synthetic merge commits, which needs further investigation. * feat: add idempotent e2e test runner script - Create .claude/run-e2e-tests.sh that idempotently: - Installs gh CLI if not present - Acquires GitHub App token - Configures git and gh auth - Runs e2e tests - Restores git config on exit - Move get_github_app_token.py to .claude directory - Add .claude/README.md documenting the scripts - Script includes colored output and proper error handling * refactor: remove color codes from e2e runner script Bots don't need pretty colors, just clear logging * feat: add token caching with expiration checking - Cache tokens to /tmp/gh_app_token_cache.json with expiration time - Reuse cached tokens if they're still valid (with 5-minute buffer) - Only generate new tokens when cache is missing or expired - Log cache status to stderr (using cached/generating new) Tokens are now reused across multiple script runs, reducing API calls and improving performance. * docs: simplify .claude docs - replace verbose README with one-line CLAUDE.md * refactor: remove redundant GITHUB_TOKEN export Only GH_TOKEN is needed - gh CLI uses it, and gh auth setup-git configures git to use gh credentials * refactor: remove internal timeout - rely on external timeout No need for duplicate timeout mechanisms * Add retry logic to PR merges in e2e tests GitHub's mergeability computation can take a few seconds after base branch changes, causing transient "not mergeable" errors. Instead of updating the branch before merging (as was done in install-gh-cli branch), add retry logic to handle these transient failures gracefully. * Add references explaining why merge retry is needed * Simplify merge_pr_with_retry calls - set -e handles failure * Restore wait_for_workflow changes: match by headBranch instead of eventPayload --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f7fc4b0 commit f79392b

1 file changed

Lines changed: 47 additions & 22 deletions

File tree

tests/test_e2e.sh

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,37 @@ cleanup() {
5151
# Trap EXIT signal to ensure cleanup runs even if the script fails
5252
trap cleanup EXIT
5353

54+
# Merge a PR with retry logic to handle transient "not mergeable" errors.
55+
# After pushing to a PR's base branch, GitHub's mergeability computation is async
56+
# and can take several seconds. During this time, merge attempts fail with
57+
# "Pull Request is not mergeable" even when there's no actual conflict.
58+
# See: https://github.com/cli/cli/issues/8092
59+
# https://github.com/orgs/community/discussions/24462
60+
merge_pr_with_retry() {
61+
local pr_url=$1
62+
local max_attempts=5
63+
local attempt=0
64+
65+
while [[ $attempt -lt $max_attempts ]]; do
66+
attempt=$((attempt + 1))
67+
echo >&2 "Merge attempt $attempt/$max_attempts for $pr_url..."
68+
69+
if log_cmd gh pr merge "$pr_url" --squash --repo "$REPO_FULL_NAME" 2>&1; then
70+
echo >&2 "PR merged successfully on attempt $attempt."
71+
return 0
72+
fi
73+
74+
if [[ $attempt -lt $max_attempts ]]; then
75+
local sleep_time=$((attempt * 2))
76+
echo >&2 "Merge failed, retrying in ${sleep_time}s..."
77+
sleep $sleep_time
78+
fi
79+
done
80+
81+
echo >&2 "Failed to merge PR after $max_attempts attempts."
82+
return 1
83+
}
84+
5485
wait_for_workflow() {
5586
local pr_number=$1 # PR number that was merged
5687
local merged_branch_name=$2 # The head branch name of the merged PR (unused now, but kept for context)
@@ -85,28 +116,22 @@ wait_for_workflow() {
85116
continue # Go to next attempt
86117
fi
87118

88-
echo >&2 "Found candidate run IDs: $candidate_run_ids. Checking payloads..."
119+
echo >&2 "Found candidate run IDs: $candidate_run_ids. Checking runs..."
89120
for run_id in $candidate_run_ids; do
90121
echo >&2 "Checking candidate run ID: $run_id"
91-
run_payload_json=$(log_cmd gh run view "$run_id" --repo "$REPO_FULL_NAME" --json eventPayload || echo "{}") # Fetch payload, default to empty JSON on error
92-
93-
# Check if the payload matches our merged PR
94-
payload_action=$(echo "$run_payload_json" | jq -r '.eventPayload.action // ""')
95-
payload_merged=$(echo "$run_payload_json" | jq -r '.eventPayload.pull_request.merged // ""')
96-
payload_pr_num=$(echo "$run_payload_json" | jq -r '.eventPayload.pull_request.number // ""')
97-
payload_merge_sha=$(echo "$run_payload_json" | jq -r '.eventPayload.pull_request.merge_commit_sha // ""')
98-
99-
# Debugging output
100-
# echo >&2 " Payload Action: $payload_action"
101-
# echo >&2 " Payload Merged: $payload_merged"
102-
# echo >&2 " Payload PR Num: $payload_pr_num"
103-
# echo >&2 " Payload Merge SHA: $payload_merge_sha"
104-
105-
if [[ "$payload_action" == "closed" && \
106-
"$payload_merged" == "true" && \
107-
"$payload_pr_num" == "$pr_number" && \
108-
"$payload_merge_sha" == "$merge_commit_sha" ]]; then
109-
echo >&2 "Found matching workflow run ID: $run_id"
122+
run_info=$(log_cmd gh run view "$run_id" --repo "$REPO_FULL_NAME" --json headBranch,headSha || echo "{}") # Fetch run info, default to empty JSON on error
123+
124+
# Check if the run matches our merged branch
125+
run_head_branch=$(echo "$run_info" | jq -r '.headBranch // ""')
126+
run_head_sha=$(echo "$run_info" | jq -r '.headSha // ""')
127+
128+
echo >&2 " Run head branch: $run_head_branch, head SHA: $run_head_sha"
129+
echo >&2 " Expected merged branch: $merged_branch_name, merge commit SHA: $merge_commit_sha"
130+
131+
# For pull_request events, the workflow runs on the PR's head branch
132+
# Match by the head branch being the merged branch name
133+
if [[ "$run_head_branch" == "$merged_branch_name" ]]; then
134+
echo >&2 "Found matching workflow run ID: $run_id (headBranch matches merged branch)"
110135
target_run_id="$run_id"
111136
break # Found the run, exit the inner loop
112137
else
@@ -268,7 +293,7 @@ echo >&2 "--- Testing Initial Merge (PR1) ---"
268293

269294
# 5. Trigger Action by Squash Merging PR1
270295
echo >&2 "5. Squash merging PR #$PR1_NUM to trigger the action..."
271-
log_cmd gh pr merge "$PR1_URL" --squash --repo "$REPO_FULL_NAME"
296+
merge_pr_with_retry "$PR1_URL"
272297
MERGE_COMMIT_SHA1=$(gh pr view "$PR1_URL" --repo "$REPO_FULL_NAME" --json mergeCommit -q .mergeCommit.oid)
273298
if [[ -z "$MERGE_COMMIT_SHA1" ]]; then
274299
echo >&2 "Failed to get merge commit SHA for PR #$PR1_NUM."
@@ -389,7 +414,7 @@ log_cmd git push origin main
389414

390415
# 9. Trigger Action by Squash Merging PR2 (which is now based on the updated main from step 7)
391416
echo >&2 "9. Squash merging PR #$PR2_NUM (feature2) to trigger conflict..."
392-
log_cmd gh pr merge "$PR2_URL" --squash --repo "$REPO_FULL_NAME"
417+
merge_pr_with_retry "$PR2_URL"
393418
MERGE_COMMIT_SHA2=$(gh pr view "$PR2_URL" --repo "$REPO_FULL_NAME" --json mergeCommit -q .mergeCommit.oid)
394419
if [[ -z "$MERGE_COMMIT_SHA2" ]]; then
395420
echo >&2 "Failed to get merge commit SHA for PR #$PR2_NUM."

0 commit comments

Comments
 (0)