| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- name: Auto-Fix Tagged Issue with OpenHands
- on:
- workflow_call:
- inputs:
- max_iterations:
- required: false
- type: number
- default: 50
- macro:
- required: false
- type: string
- default: "@openhands-agent"
- target_branch:
- required: false
- type: string
- default: "main"
- description: "Target branch to pull and create PR against"
- LLM_MODEL:
- required: false
- type: string
- default: "anthropic/claude-3-5-sonnet-20241022"
- base_container_image:
- required: false
- type: string
- default: ""
- description: "Custom sandbox env"
- secrets:
- LLM_MODEL:
- required: false
- LLM_API_KEY:
- required: true
- LLM_BASE_URL:
- required: false
- PAT_TOKEN:
- required: false
- PAT_USERNAME:
- required: false
- issues:
- types: [labeled]
- pull_request:
- types: [labeled]
- issue_comment:
- types: [created]
- pull_request_review_comment:
- types: [created]
- pull_request_review:
- types: [submitted]
- permissions:
- contents: write
- pull-requests: write
- issues: write
- jobs:
- auto-fix:
- if: |
- github.event_name == 'workflow_call' ||
- github.event.label.name == 'fix-me' ||
- github.event.label.name == 'fix-me-experimental' ||
- (
- ((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
- contains(github.event.comment.body, inputs.macro || '@openhands-agent') &&
- (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR' || github.event.comment.author_association == 'MEMBER')
- ) ||
- (github.event_name == 'pull_request_review' &&
- contains(github.event.review.body, inputs.macro || '@openhands-agent') &&
- (github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'COLLABORATOR' || github.event.review.author_association == 'MEMBER')
- )
- )
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: "3.12"
- - name: Get latest versions and create requirements.txt
- run: |
- python -m pip index versions openhands-ai > openhands_versions.txt
- OPENHANDS_VERSION=$(head -n 1 openhands_versions.txt | awk '{print $2}' | tr -d '()')
- echo "openhands-ai==${OPENHANDS_VERSION}" >> requirements.txt
- cat requirements.txt
- - name: Cache pip dependencies
- if: |
- !(
- github.event.label.name == 'fix-me-experimental' ||
- (
- (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
- contains(github.event.comment.body, '@openhands-agent-exp')
- ) ||
- (
- github.event_name == 'pull_request_review' &&
- contains(github.event.review.body, '@openhands-agent-exp')
- )
- )
- uses: actions/cache@v3
- with:
- path: ${{ env.pythonLocation }}/lib/python3.12/site-packages/*
- key: ${{ runner.os }}-pip-openhands-resolver-${{ hashFiles('requirements.txt') }}
- restore-keys: |
- ${{ runner.os }}-pip-openhands-resolver-${{ hashFiles('requirements.txt') }}
- - name: Check required environment variables
- env:
- LLM_MODEL: ${{ secrets.LLM_MODEL || inputs.LLM_MODEL }}
- LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
- LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
- PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
- PAT_USERNAME: ${{ secrets.PAT_USERNAME }}
- GITHUB_TOKEN: ${{ github.token }}
- run: |
- required_vars=("LLM_MODEL" "LLM_API_KEY")
- for var in "${required_vars[@]}"; do
- if [ -z "${!var}" ]; then
- echo "Error: Required environment variable $var is not set."
- exit 1
- fi
- done
- # Check optional variables and warn about fallbacks
- if [ -z "$PAT_TOKEN" ]; then
- echo "Warning: PAT_TOKEN is not set, falling back to GITHUB_TOKEN"
- fi
- if [ -z "$LLM_BASE_URL" ]; then
- echo "Warning: LLM_BASE_URL is not set, will use default API endpoint"
- fi
- if [ -z "$PAT_USERNAME" ]; then
- echo "Warning: PAT_USERNAME is not set, will use openhands-agent"
- fi
- - name: Set environment variables
- run: |
- if [ -n "${{ github.event.review.body }}" ]; then
- echo "ISSUE_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
- echo "ISSUE_TYPE=pr" >> $GITHUB_ENV
- elif [ -n "${{ github.event.issue.pull_request }}" ]; then
- echo "ISSUE_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV
- echo "ISSUE_TYPE=pr" >> $GITHUB_ENV
- elif [ -n "${{ github.event.pull_request.number }}" ]; then
- echo "ISSUE_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
- echo "ISSUE_TYPE=pr" >> $GITHUB_ENV
- else
- echo "ISSUE_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV
- echo "ISSUE_TYPE=issue" >> $GITHUB_ENV
- fi
- if [ -n "${{ github.event.review.body }}" ]; then
- echo "COMMENT_ID=${{ github.event.review.id || 'None' }}" >> $GITHUB_ENV
- else
- echo "COMMENT_ID=${{ github.event.comment.id || 'None' }}" >> $GITHUB_ENV
- fi
- echo "MAX_ITERATIONS=${{ inputs.max_iterations || 50 }}" >> $GITHUB_ENV
- echo "SANDBOX_ENV_GITHUB_TOKEN=${{ secrets.PAT_TOKEN || github.token }}" >> $GITHUB_ENV
- echo "SANDBOX_ENV_BASE_CONTAINER_IMAGE=${{ inputs.base_container_image }}" >> $GITHUB_ENV
- # Set branch variables
- echo "TARGET_BRANCH=${{ inputs.target_branch }}" >> $GITHUB_ENV
- - name: Comment on issue with start message
- uses: actions/github-script@v7
- with:
- github-token: ${{ secrets.PAT_TOKEN || github.token }}
- script: |
- const issueType = process.env.ISSUE_TYPE;
- github.rest.issues.createComment({
- issue_number: ${{ env.ISSUE_NUMBER }},
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: `[OpenHands](https://github.com/All-Hands-AI/OpenHands) started fixing the ${issueType}! You can monitor the progress [here](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).`
- });
- - name: Install OpenHands
- run: |
- if [[ "${{ github.event.label.name }}" == "fix-me-experimental" ]] ||
- ([[ "${{ github.event_name }}" == "issue_comment" || "${{ github.event_name }}" == "pull_request_review_comment" ]] &&
- [[ "${{ github.event.comment.body }}" == "@openhands-agent-exp"* ]]) ||
- ([[ "${{ github.event_name }}" == "pull_request_review" ]] &&
- [[ "${{ github.event.review.body }}" == "@openhands-agent-exp"* ]]); then
- python -m pip install --upgrade pip
- pip install git+https://github.com/all-hands-ai/openhands.git
- else
- python -m pip install --upgrade -r requirements.txt
- fi
- - name: Attempt to resolve issue
- env:
- GITHUB_TOKEN: ${{ secrets.PAT_TOKEN || github.token }}
- GITHUB_USERNAME: ${{ secrets.PAT_USERNAME || 'openhands-agent' }}
- LLM_MODEL: ${{ secrets.LLM_MODEL || inputs.LLM_MODEL }}
- LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
- LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
- PYTHONPATH: ""
- run: |
- cd /tmp && python -m openhands.resolver.resolve_issue \
- --repo ${{ github.repository }} \
- --issue-number ${{ env.ISSUE_NUMBER }} \
- --issue-type ${{ env.ISSUE_TYPE }} \
- --max-iterations ${{ env.MAX_ITERATIONS }} \
- --comment-id ${{ env.COMMENT_ID }}
- - name: Check resolution result
- id: check_result
- run: |
- if cd /tmp && grep -q '"success":true' output/output.jsonl; then
- echo "RESOLUTION_SUCCESS=true" >> $GITHUB_OUTPUT
- else
- echo "RESOLUTION_SUCCESS=false" >> $GITHUB_OUTPUT
- fi
- - name: Upload output.jsonl as artifact
- uses: actions/upload-artifact@v4
- if: always() # Upload even if the previous steps fail
- with:
- name: resolver-output
- path: /tmp/output/output.jsonl
- retention-days: 30 # Keep the artifact for 30 days
- - name: Create draft PR or push branch
- if: always() # Create PR or branch even if the previous steps fail
- env:
- GITHUB_TOKEN: ${{ secrets.PAT_TOKEN || github.token }}
- GITHUB_USERNAME: ${{ secrets.PAT_USERNAME || 'openhands-agent' }}
- LLM_MODEL: ${{ secrets.LLM_MODEL || inputs.LLM_MODEL }}
- LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
- LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
- PYTHONPATH: ""
- run: |
- if [ "${{ steps.check_result.outputs.RESOLUTION_SUCCESS }}" == "true" ]; then
- cd /tmp && python -m openhands.resolver.send_pull_request \
- --issue-number ${{ env.ISSUE_NUMBER }} \
- --pr-type draft \
- --reviewer ${{ github.actor }} | tee pr_result.txt && \
- grep "draft created" pr_result.txt | sed 's/.*\///g' > pr_number.txt
- else
- cd /tmp && python -m openhands.resolver.send_pull_request \
- --issue-number ${{ env.ISSUE_NUMBER }} \
- --pr-type branch \
- --send-on-failure | tee branch_result.txt && \
- grep "branch created" branch_result.txt | sed 's/.*\///g; s/.expand=1//g' > branch_name.txt
- fi
- - name: Comment on issue
- uses: actions/github-script@v7
- if: always() # Comment on issue even if the previous steps fail
- with:
- github-token: ${{ secrets.PAT_TOKEN || github.token }}
- script: |
- const fs = require('fs');
- const issueNumber = ${{ env.ISSUE_NUMBER }};
- const success = ${{ steps.check_result.outputs.RESOLUTION_SUCCESS }};
- let prNumber = '';
- let branchName = '';
- let logContent = '';
- const noChangesMessage = `No changes to commit for issue #${issueNumber}. Skipping commit.`;
- try {
- if (success){
- logContent = fs.readFileSync('/tmp/pr_result.txt', 'utf8').trim();
- } else {
- logContent = fs.readFileSync('/tmp/branch_result.txt', 'utf8').trim();
- }
- } catch (error) {
- console.error('Error reading results file:', error);
- }
- try {
- if (success) {
- prNumber = fs.readFileSync('/tmp/pr_number.txt', 'utf8').trim();
- } else {
- branchName = fs.readFileSync('/tmp/branch_name.txt', 'utf8').trim();
- }
- } catch (error) {
- console.error('Error reading file:', error);
- }
- if (logContent.includes(noChangesMessage)) {
- github.rest.issues.createComment({
- issue_number: issueNumber,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: `The workflow to fix this issue encountered an error. Openhands failed to create any code changes.`
- });
- } else if (success && prNumber) {
- github.rest.issues.createComment({
- issue_number: issueNumber,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: `A potential fix has been generated and a draft PR #${prNumber} has been created. Please review the changes.`
- });
- } else if (!success && branchName) {
- github.rest.issues.createComment({
- issue_number: issueNumber,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: `An attempt was made to automatically fix this issue, but it was unsuccessful. A branch named '${branchName}' has been created with the attempted changes. You can view the branch [here](https://github.com/${context.repo.owner}/${context.repo.repo}/tree/${branchName}). Manual intervention may be required.`
- });
- } else {
- github.rest.issues.createComment({
- issue_number: issueNumber,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: `The workflow to fix this issue encountered an error. Please check the [workflow logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) for more information.`
- });
- }
|