Skip to content

Commit 3964e50

Browse files
fix: remove /update command from merge-bot (not supported for fork PRs) (#38)
* docs: Add CLAUDE.md with project guidelines - Complete project structure overview - Common commands and workflows - Release process (always use ./scripts/release.sh) - Architecture details and optimizations - Linting, testing, and CI/CD guides - Troubleshooting tips This file provides context for Claude Code to work more effectively with the project without repeating instructions. * Add CHANGELOG.md for release tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: remove /update command from merge-bot (not supported for fork PRs) Removed the entire `update-command` job from merge-bot workflow. **Reason:** The `/update` command does not work for PRs from forks due to GitHub security restrictions. GitHub Actions cannot push to fork repositories, so the command always fails with a permission error. **Impact:** - Removed 369 lines of non-functional code - Users will no longer see confusing "not supported for forks" messages - Maintainers can still update branches manually with git commands **For contributors:** To update your fork's branch with upstream changes: ```bash git fetch upstream main git merge upstream/main git push ``` This is the only reliable way to update fork PRs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ede227a commit 3964e50

File tree

1 file changed

+0
-369
lines changed

1 file changed

+0
-369
lines changed

.github/workflows/merge-bot.yml

Lines changed: 0 additions & 369 deletions
Original file line numberDiff line numberDiff line change
@@ -287,372 +287,3 @@ jobs:
287287
} catch (error) {
288288
core.warning(`Failed to delete branch: ${error.message}`);
289289
}
290-
291-
update-command:
292-
name: Handle Update Command
293-
runs-on: ubuntu-latest
294-
# Only run on PR comments
295-
if: github.event.issue.pull_request
296-
steps:
297-
- name: Parse command
298-
id: command
299-
uses: actions/github-script@v8
300-
with:
301-
script: |
302-
const comment = context.payload.comment.body.toLowerCase().trim();
303-
const user = context.payload.comment.user.login;
304-
305-
core.info(`Comment from ${user}: ${comment}`);
306-
307-
// Check for update command (case insensitive)
308-
// Supported formats:
309-
// - @mergebot update
310-
// - @merge-bot update
311-
// - /update
312-
// - update (if alone)
313-
const updatePatterns = [
314-
/@merge-?bot\s+update/i,
315-
/^\/update$/i,
316-
/^update$/i,
317-
];
318-
319-
const isUpdateCommand = updatePatterns.some(pattern => pattern.test(comment));
320-
321-
if (!isUpdateCommand) {
322-
core.info('Not an update command, skipping');
323-
return;
324-
}
325-
326-
core.setOutput('should_update', 'true');
327-
core.info('✅ Update command detected');
328-
329-
- name: React to comment
330-
if: steps.command.outputs.should_update == 'true'
331-
uses: actions/github-script@v8
332-
with:
333-
script: |
334-
// Add eyes emoji to show bot is processing
335-
await github.rest.reactions.createForIssueComment({
336-
owner: context.repo.owner,
337-
repo: context.repo.repo,
338-
comment_id: context.payload.comment.id,
339-
content: 'eyes'
340-
});
341-
342-
- name: Check permissions
343-
if: steps.command.outputs.should_update == 'true'
344-
id: check_perms
345-
uses: actions/github-script@v8
346-
with:
347-
script: |
348-
const user = context.payload.comment.user.login;
349-
350-
// Check if user has write access
351-
try {
352-
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
353-
owner: context.repo.owner,
354-
repo: context.repo.repo,
355-
username: user
356-
});
357-
358-
const hasPermission = ['admin', 'write'].includes(permission.permission);
359-
360-
if (!hasPermission) {
361-
core.setFailed(`❌ @${user} does not have permission to update PRs (permission: ${permission.permission})`);
362-
363-
// Comment on PR
364-
await github.rest.issues.createComment({
365-
owner: context.repo.owner,
366-
repo: context.repo.repo,
367-
issue_number: context.payload.issue.number,
368-
body: `❌ @${user} you don't have permission to update PRs. Only collaborators with write access can use update commands.`
369-
});
370-
371-
return;
372-
}
373-
374-
core.info(`✅ User ${user} has ${permission.permission} access`);
375-
core.setOutput('has_permission', 'true');
376-
} catch (error) {
377-
core.setFailed(`Error checking permissions: ${error.message}`);
378-
}
379-
380-
- name: Get PR info
381-
if: steps.check_perms.outputs.has_permission == 'true'
382-
id: pr_info
383-
uses: actions/github-script@v8
384-
with:
385-
script: |
386-
const { data: pr } = await github.rest.pulls.get({
387-
owner: context.repo.owner,
388-
repo: context.repo.repo,
389-
pull_number: context.payload.issue.number
390-
});
391-
392-
// Debug information
393-
core.info(`PR #${pr.number}: ${pr.title}`);
394-
core.info(`- Base: ${pr.base.ref} (repo: ${pr.base.repo.full_name})`);
395-
core.info(`- Head: ${pr.head.ref} (repo: ${pr.head.repo.full_name})`);
396-
core.info(`- Same repo: ${pr.head.repo.full_name === pr.base.repo.full_name}`);
397-
398-
// Validate branch information
399-
if (!pr.head.ref || !pr.base.ref) {
400-
core.setFailed('Invalid PR branch information');
401-
return;
402-
}
403-
404-
// Check if it's from the same repository
405-
const isSameRepo = pr.head.repo.full_name === pr.base.repo.full_name;
406-
407-
// Early exit for fork PRs
408-
if (!isSameRepo) {
409-
core.info('❌ PR is from fork - update not supported');
410-
core.setOutput('is_fork', 'true');
411-
412-
const baseBranch = pr.base.ref;
413-
const forkMessage = [
414-
'ℹ️ Update commands are not supported for PRs from forks.',
415-
'',
416-
'To update your branch, please run these commands locally:',
417-
'',
418-
'```bash',
419-
`git fetch upstream ${baseBranch}`,
420-
`git merge upstream/${baseBranch}`,
421-
'git push',
422-
'```',
423-
'',
424-
'Or create a new PR from the same repository.'
425-
].join('\n');
426-
427-
await github.rest.issues.createComment({
428-
owner: context.repo.owner,
429-
repo: context.repo.repo,
430-
issue_number: context.payload.issue.number,
431-
body: forkMessage
432-
});
433-
434-
return;
435-
}
436-
437-
core.setOutput('base_branch', pr.base.ref);
438-
core.setOutput('head_branch', pr.head.ref);
439-
core.setOutput('head_repo', pr.head.repo.full_name);
440-
core.setOutput('base_repo', pr.base.repo.full_name);
441-
core.setOutput('is_same_repo', isSameRepo.toString());
442-
443-
- name: Validate branch exists
444-
if: steps.check_perms.outputs.has_permission == 'true' && steps.pr_info.outputs.is_fork != 'true'
445-
id: validate_branch
446-
uses: actions/github-script@v8
447-
with:
448-
script: |
449-
const headBranch = '${{ steps.pr_info.outputs.head_branch }}';
450-
const headRepo = '${{ steps.pr_info.outputs.head_repo }}';
451-
const isSameRepo = '${{ steps.pr_info.outputs.is_same_repo }}' === 'true';
452-
453-
core.info(`Validating branch: ${headBranch} from repo: ${headRepo}`);
454-
455-
try {
456-
// Check if branch exists
457-
const { data: branch } = await github.rest.repos.getBranch({
458-
owner: headRepo.split('/')[0],
459-
repo: headRepo.split('/')[1],
460-
branch: headBranch
461-
});
462-
463-
core.info(`✅ Branch exists: ${headBranch} (SHA: ${branch.commit.sha})`);
464-
core.setOutput('branch_exists', 'true');
465-
466-
} catch (error) {
467-
if (error.status === 404) {
468-
core.setOutput('branch_exists', 'false');
469-
core.error(`❌ Branch not found: ${headBranch}`);
470-
471-
// Provide helpful error message
472-
let errorMessage = `❌ Cannot update branch: Branch \`${headBranch}\` not found.`;
473-
474-
if (!isSameRepo) {
475-
errorMessage += '\n\nThis appears to be a PR from a fork. Update commands only work for branches in the same repository.';
476-
} else {
477-
errorMessage += '\n\nThis could mean:\n';
478-
errorMessage += '- The branch was deleted after merge\n';
479-
errorMessage += '- The branch name changed\n';
480-
errorMessage += '- There was a force push that changed the branch reference';
481-
}
482-
483-
await github.rest.issues.createComment({
484-
owner: context.repo.owner,
485-
repo: context.repo.repo,
486-
issue_number: context.payload.issue.number,
487-
body: errorMessage
488-
});
489-
490-
return;
491-
}
492-
493-
core.setFailed(`Error checking branch: ${error.message}`);
494-
}
495-
496-
- name: Check if update is needed
497-
if: steps.check_perms.outputs.has_permission == 'true' && steps.validate_branch.outputs.branch_exists == 'true'
498-
id: check_update
499-
uses: actions/github-script@v8
500-
with:
501-
script: |
502-
const baseBranch = '${{ steps.pr_info.outputs.base_branch }}';
503-
const headBranch = '${{ steps.pr_info.outputs.head_branch }}';
504-
505-
try {
506-
// Get latest commit from base branch
507-
const { data: baseBranchData } = await github.rest.repos.getBranch({
508-
owner: context.repo.owner,
509-
repo: context.repo.repo,
510-
branch: baseBranch
511-
});
512-
513-
const baseSha = baseBranchData.commit.sha;
514-
515-
// Check if head branch is behind base (same-repo PRs only at this point)
516-
const { data: comparison } = await github.rest.repos.compareCommits({
517-
owner: context.repo.owner,
518-
repo: context.repo.repo,
519-
base: baseSha,
520-
head: headBranch
521-
});
522-
523-
const isBehind = comparison.behind_by > 0;
524-
const aheadBy = comparison.ahead_by;
525-
const behindBy = comparison.behind_by;
526-
527-
core.info(`Branch comparison: ${behindBy} commits behind, ${aheadBy} commits ahead`);
528-
529-
if (!isBehind) {
530-
core.info('✅ Branch is already up to date');
531-
core.setOutput('update_needed', 'false');
532-
core.setOutput('already_up_to_date', 'true');
533-
} else {
534-
core.info(`Update needed: ${behindBy} commits behind`);
535-
core.setOutput('update_needed', 'true');
536-
core.setOutput('commits_behind', behindBy.toString());
537-
}
538-
539-
} catch (error) {
540-
core.setFailed(`Error checking if update is needed: ${error.message}`);
541-
}
542-
543-
- name: Handle already up-to-date
544-
if: steps.check_perms.outputs.has_permission == 'true' && steps.check_update.outputs.already_up_to_date == 'true'
545-
uses: actions/github-script@v8
546-
with:
547-
script: |
548-
await github.rest.issues.createComment({
549-
owner: context.repo.owner,
550-
repo: context.repo.repo,
551-
issue_number: context.payload.issue.number,
552-
body: '✅ Branch is already up to date with the latest changes!'
553-
});
554-
555-
- name: Checkout PR branch
556-
if: steps.check_perms.outputs.has_permission == 'true' && steps.check_update.outputs.update_needed == 'true' && steps.validate_branch.outputs.branch_exists == 'true'
557-
uses: actions/checkout@v4
558-
with:
559-
repository: ${{ steps.pr_info.outputs.head_repo }}
560-
ref: ${{ steps.pr_info.outputs.head_branch }}
561-
fetch-depth: 0
562-
token: ${{ secrets.GITHUB_TOKEN }}
563-
564-
- name: Update branch
565-
if: steps.check_perms.outputs.has_permission == 'true' && steps.check_update.outputs.update_needed == 'true' && steps.validate_branch.outputs.branch_exists == 'true'
566-
id: update
567-
run: |
568-
BASE_BRANCH="${{ steps.pr_info.outputs.base_branch }}"
569-
HEAD_BRANCH="${{ steps.pr_info.outputs.head_branch }}"
570-
COMMITS_BEHIND="${{ steps.check_update.outputs.commits_behind }}"
571-
572-
git config user.name "github-actions[bot]"
573-
git config user.email "github-actions[bot]@users.noreply.github.com"
574-
575-
echo "Fetching $BASE_BRANCH..."
576-
git fetch origin "$BASE_BRANCH"
577-
578-
echo "Merging origin/$BASE_BRANCH into $HEAD_BRANCH..."
579-
if git merge "origin/$BASE_BRANCH" -m "chore: Update branch with latest changes from $BASE_BRANCH"; then
580-
echo "merge_success=true" >> $GITHUB_OUTPUT
581-
echo "✅ Branch updated successfully ($COMMITS_BEHIND commits merged)"
582-
else
583-
echo "merge_success=false" >> $GITHUB_OUTPUT
584-
git merge --abort || true
585-
echo "❌ Merge failed - conflicts detected"
586-
exit 1
587-
fi
588-
589-
- name: Push changes
590-
if: steps.update.outputs.merge_success == 'true'
591-
run: |
592-
HEAD_BRANCH="${{ steps.pr_info.outputs.head_branch }}"
593-
HEAD_REPO="${{ steps.pr_info.outputs.head_repo }}"
594-
595-
echo "Pushing changes to $HEAD_BRANCH in $HEAD_REPO..."
596-
git push origin "$HEAD_BRANCH"
597-
598-
- name: Comment success
599-
if: steps.update.outputs.merge_success == 'true'
600-
uses: actions/github-script@v8
601-
with:
602-
script: |
603-
const user = context.payload.comment.user.login;
604-
const baseBranch = '${{ steps.pr_info.outputs.base_branch }}';
605-
const commitsBehind = '${{ steps.check_update.outputs.commits_behind }}';
606-
607-
const successMessage = `✅ Branch updated successfully by @${user}!\n\nMerged ${commitsBehind} commit(s) from \`${baseBranch}\`.`;
608-
609-
await github.rest.issues.createComment({
610-
owner: context.repo.owner,
611-
repo: context.repo.repo,
612-
issue_number: context.payload.issue.number,
613-
body: successMessage
614-
});
615-
616-
// Add +1 reaction to original comment
617-
await github.rest.reactions.createForIssueComment({
618-
owner: context.repo.owner,
619-
repo: context.repo.repo,
620-
comment_id: context.payload.comment.id,
621-
content: '+1'
622-
});
623-
624-
- name: Comment failure
625-
if: failure() && steps.check_perms.outputs.has_permission == 'true'
626-
uses: actions/github-script@v8
627-
with:
628-
script: |
629-
const baseBranch = '${{ steps.pr_info.outputs.base_branch }}';
630-
631-
const failureMessage = [
632-
`❌ Failed to update branch with latest changes from \`${baseBranch}\`.`,
633-
'',
634-
'This usually means there are merge conflicts that need to be resolved manually.',
635-
'',
636-
'Please update your branch locally:',
637-
'```bash',
638-
`git fetch origin ${baseBranch}`,
639-
`git merge origin/${baseBranch}`,
640-
'# Resolve conflicts',
641-
'git push',
642-
'```'
643-
].join('\n');
644-
645-
await github.rest.issues.createComment({
646-
owner: context.repo.owner,
647-
repo: context.repo.repo,
648-
issue_number: context.payload.issue.number,
649-
body: failureMessage
650-
});
651-
652-
// Add -1 reaction to original comment
653-
await github.rest.reactions.createForIssueComment({
654-
owner: context.repo.owner,
655-
repo: context.repo.repo,
656-
comment_id: context.payload.comment.id,
657-
content: '-1'
658-
});

0 commit comments

Comments
 (0)