Skip to content

fix(ci): amber-issue-handler.yml - complete file for direct edit #593

@jeremyeder

Description

@jeremyeder

Context

PR #592 merged the workflows: write permission fix but had failing CI tests. This issue provides the complete, corrected .github/workflows/amber-issue-handler.yml file for direct editing.

File: .github/workflows/amber-issue-handler.yml

Only change from previous version: Added workflows: write permission (line 33) so Amber can push branches containing .github/workflows/ files.

Original error: Run #21777412886

refusing to allow a GitHub App to create or update workflow
`.github/workflows/nightly-builds.yml` without `workflows` permission

Complete File

Click to expand full amber-issue-handler.yml
# Amber Issue-to-PR Handler
#
# This workflow automates issue resolution via the Amber background agent.
#
# TRIGGERS:
# - Issue labeled with: amber:auto-fix, amber:refactor, amber:test-coverage
# - Issue comment containing: /amber execute or @amber
#
# BEHAVIOR:
# - Checks for existing open PR for the issue (prevents duplicate PRs)
# - Creates or updates feature branch: amber/issue-{number}-{sanitized-title}
# - Runs Claude Code to implement changes
# - Creates PR or pushes to existing PR
#
# SECURITY:
# - Validates branch names against injection attacks
# - Uses strict regex matching for PR lookup
# - Handles race conditions when PRs are closed during execution

name: Amber Issue-to-PR Handler

on:
  issues:
    types: [labeled, opened]
  issue_comment:
    types: [created]

permissions:
  contents: write
  issues: write
  pull-requests: write
  id-token: write    # Required for OIDC token (Bedrock/Vertex/Foundry/OAuth)
  workflows: write   # Required to create/update GitHub Actions workflow files

jobs:
  amber-handler:
    runs-on: ubuntu-latest
    timeout-minutes: 30  # Issue #7: Prevent runaway jobs
    # Only run for specific labels, commands, or @amber mentions
    if: |
      (github.event.label.name == 'amber:auto-fix' ||
       github.event.label.name == 'amber:refactor' ||
       github.event.label.name == 'amber:test-coverage' ||
       contains(github.event.comment.body, '/amber execute') ||
       contains(github.event.comment.body, '@amber'))

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Determine Amber action type
        id: action-type
        env:
          LABEL_NAME: ${{ github.event.label.name }}
          COMMENT_BODY: ${{ github.event.comment.body }}
        run: |
          # Parse label or comment to determine action
          if [[ "$LABEL_NAME" == "amber:auto-fix" ]]; then
            echo "type=auto-fix" >> $GITHUB_OUTPUT
            echo "severity=low" >> $GITHUB_OUTPUT
          elif [[ "$LABEL_NAME" == "amber:refactor" ]]; then
            echo "type=refactor" >> $GITHUB_OUTPUT
            echo "severity=medium" >> $GITHUB_OUTPUT
          elif [[ "$LABEL_NAME" == "amber:test-coverage" ]]; then
            echo "type=test-coverage" >> $GITHUB_OUTPUT
            echo "severity=medium" >> $GITHUB_OUTPUT
          elif [[ "$COMMENT_BODY" == *"/amber execute"* ]] || [[ "$COMMENT_BODY" == *"@amber"* ]]; then
            # Treat @amber mentions same as /amber execute - let Claude figure out the intent
            echo "type=execute-proposal" >> $GITHUB_OUTPUT
            echo "severity=medium" >> $GITHUB_OUTPUT
          else
            echo "type=unknown" >> $GITHUB_OUTPUT
            exit 1
          fi

      - name: Extract issue details
        id: issue-details
        uses: actions/github-script@v8
        with:
          script: |
            const issue = context.payload.issue;

            // Parse issue body for Amber-compatible context
            const body = issue.body || '';

            // Extract file paths mentioned in issue
            const filePattern = /(?:File|Path):\s*`?([^\s`]+)`?/gi;
            const files = [...body.matchAll(filePattern)].map(m => m[1]);

            // Extract specific instructions
            const instructionPattern = /(?:Instructions?|Task):\s*\n([\s\S]*?)(?:\n#{2,}|\n---|\n\*\*|$)/i;
            const instructionMatch = body.match(instructionPattern);
            const instructions = instructionMatch ? instructionMatch[1].trim() : '';

            // Set outputs
            core.setOutput('issue_number', issue.number);
            core.setOutput('issue_title', issue.title);
            core.setOutput('issue_body', body);
            core.setOutput('files', JSON.stringify(files));
            core.setOutput('instructions', instructions || issue.title);

            console.log('Parsed issue:', {
              number: issue.number,
              title: issue.title,
              files: files,
              instructions: instructions || issue.title
            });

      - name: Create Amber agent prompt
        id: create-prompt
        env:
          ISSUE_NUMBER: ${{ steps.issue-details.outputs.issue_number }}
          ISSUE_TITLE: ${{ steps.issue-details.outputs.issue_title }}
          ISSUE_INSTRUCTIONS: ${{ steps.issue-details.outputs.instructions }}
          ISSUE_FILES: ${{ steps.issue-details.outputs.files }}
          ACTION_TYPE: ${{ steps.action-type.outputs.type }}
          ACTION_SEVERITY: ${{ steps.action-type.outputs.severity }}
        run: |
          cat > /tmp/amber-prompt.md <<'EOF'
          # Amber Agent Task: Issue #${ISSUE_NUMBER}

          **Action Type:** ${ACTION_TYPE}
          **Severity:** ${ACTION_SEVERITY}

          ## Issue Details
          **Title:** ${ISSUE_TITLE}

          **Instructions:**
          ${ISSUE_INSTRUCTIONS}

          **Files to modify (if specified):**
          ${ISSUE_FILES}

          ## Your Mission

          Based on the action type, perform the following:

          ### For `auto-fix` type:
          1. Identify the specific linting/formatting issues mentioned
          2. Run appropriate formatters (gofmt, black, prettier, etc.)
          3. Fix any trivial issues (unused imports, spacing, etc.)
          4. Ensure all changes pass existing tests
          5. Create a clean commit with conventional format

          ### For `refactor` type:
          1. Analyze the current code structure
          2. Implement the refactoring as described in the issue
          3. Ensure backward compatibility (no breaking changes)
          4. Add/update tests to cover refactored code
          5. Verify all existing tests still pass

          ### For `test-coverage` type:
          1. Analyze current test coverage for specified files
          2. Identify untested code paths
          3. Write contract tests following project standards (see CLAUDE.md)
          4. Ensure tests follow table-driven test pattern (Go) or pytest patterns (Python)
          5. Verify all new tests pass

          ### For `execute-proposal` type:
          1. Read the full issue body for the proposed implementation
          2. Execute the changes as specified in the proposal
          3. Follow the risk assessment and rollback plan provided
          4. Ensure all testing strategies are implemented

          ## Requirements

          - Follow all standards in `CLAUDE.md`
          - Use conventional commit format: `type(scope): message`
          - Run all linters BEFORE committing:
            - Go: `gofmt -w .`, `golangci-lint run`
            - Python: `black .`, `isort .`, `flake8`
            - TypeScript: `npm run lint`
          - Ensure ALL tests pass: `make test`
          - Create branch following pattern: `amber/issue-${ISSUE_NUMBER}-{description}`

          ## Success Criteria

          - All linters pass with 0 warnings
          - All existing tests pass
          - New code follows project conventions
          - Commit message is clear and follows conventional format
          - Changes are focused on issue scope (no scope creep)

          ## Output Format

          After completing the work, provide:
          1. **Summary of changes** (2-3 sentences)
          2. **Files modified** (list with line count changes)
          3. **Test results** (pass/fail for each test suite)
          4. **Linting results** (confirm all pass)
          5. **Commit SHA**

          Ready to execute!
          EOF

          # Substitute environment variables
          envsubst < /tmp/amber-prompt.md > amber-prompt.md

          echo "prompt_file=amber-prompt.md" >> $GITHUB_OUTPUT

      - name: Check for existing PR
        id: check-existing-pr
        env:
          ISSUE_NUMBER: ${{ steps.issue-details.outputs.issue_number }}
          GH_TOKEN: ${{ github.token }}
        run: |
          # Validate issue number is numeric to prevent injection
          if ! [[ "$ISSUE_NUMBER" =~ ^[0-9]+$ ]]; then
            echo "Error: Invalid issue number format"
            exit 1
          fi

          # Check if there's already an open PR for this issue using stricter matching
          # Search for PRs that reference this issue and filter by body containing exact "Closes #N" pattern
          EXISTING_PR=$(gh pr list --state open --json number,headRefName,body --jq \
            ".[] | select(.body | test(\"Closes #${ISSUE_NUMBER}($|[^0-9])\")) | {number, headRefName}" \
            2>/dev/null | head -1 || echo "")

          if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ] && [ "$EXISTING_PR" != "{}" ]; then
            PR_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number')
            EXISTING_BRANCH=$(echo "$EXISTING_PR" | jq -r '.headRefName')

            # Validate branch name format to prevent command injection
            if ! [[ "$EXISTING_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
              echo "Error: Invalid branch name format in existing PR"
              echo "existing_pr=false" >> $GITHUB_OUTPUT
              exit 0
            fi

            echo "existing_pr=true" >> $GITHUB_OUTPUT
            echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
            echo "existing_branch=$EXISTING_BRANCH" >> $GITHUB_OUTPUT
            echo "Found existing PR #$PR_NUMBER on branch $EXISTING_BRANCH"
          else
            echo "existing_pr=false" >> $GITHUB_OUTPUT
            echo "No existing PR found for issue #${ISSUE_NUMBER}"
          fi

      - name: Create or checkout feature branch
        id: create-branch
        env:
          ISSUE_NUMBER: ${{ steps.issue-details.outputs.issue_number }}
          ISSUE_TITLE: ${{ steps.issue-details.outputs.issue_title }}
          EXISTING_PR: ${{ steps.check-existing-pr.outputs.existing_pr }}
          EXISTING_BRANCH: ${{ steps.check-existing-pr.outputs.existing_branch }}
        run: |
          git config user.name "Amber Agent"
          git config user.email "amber@ambient-code.ai"

          # Validate issue number format
          if ! [[ "$ISSUE_NUMBER" =~ ^[0-9]+$ ]]; then
            echo "Error: Invalid issue number format"
            exit 1
          fi

          checkout_branch() {
            local branch="$1"
            local is_existing="$2"

            # Validate branch name format (alphanumeric, slashes, dashes, dots, underscores only)
            if ! [[ "$branch" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
              echo "Error: Invalid branch name format: $branch"
              return 1
            fi

            echo "Attempting to checkout branch: $branch"
            if git fetch origin "$branch" 2>/dev/null; then
              git checkout -B "$branch" "origin/$branch"
              echo "Checked out existing remote branch: $branch"
            elif [ "$is_existing" == "true" ]; then
              # Race condition: PR existed but branch was deleted
              echo "Warning: Branch $branch no longer exists on remote (PR may have been closed)"
              return 1
            else
              echo "Creating new branch: $branch"
              git checkout -b "$branch"
            fi
            return 0
          }

          if [ "$EXISTING_PR" == "true" ] && [ -n "$EXISTING_BRANCH" ]; then
            # Try to checkout existing PR branch with race condition handling
            if ! checkout_branch "$EXISTING_BRANCH" "true"; then
              echo "Existing PR branch unavailable, falling back to new branch creation"
              # Fall through to create new branch
              EXISTING_PR="false"
            else
              BRANCH_NAME="$EXISTING_BRANCH"
            fi
          fi

          if [ "$EXISTING_PR" != "true" ]; then
            # Create new branch with sanitized title
            # Sanitization: lowercase, replace non-alphanumeric with dash, collapse dashes, trim
            SANITIZED_TITLE=$(echo "$ISSUE_TITLE" \
              | tr '[:upper:]' '[:lower:]' \
              | sed 's/[^a-z0-9-]/-/g' \
              | sed 's/--*/-/g' \
              | sed 's/^-//' \
              | sed 's/-$//' \
              | cut -c1-50)

            BRANCH_NAME="amber/issue-${ISSUE_NUMBER}-${SANITIZED_TITLE}"

            # Validate the generated branch name
            if ! [[ "$BRANCH_NAME" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
              echo "Error: Generated branch name is invalid: $BRANCH_NAME"
              exit 1
            fi

            checkout_branch "$BRANCH_NAME" "false" || exit 1
          fi

          echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
          echo "Using branch: $BRANCH_NAME"

      - name: Read prompt file
        id: read-prompt
        run: |
          PROMPT_CONTENT=$(cat amber-prompt.md)
          # Use heredoc to safely handle multiline content
          echo "prompt<<EOF" >> $GITHUB_OUTPUT
          cat amber-prompt.md >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Install Claude Code CLI
        run: |
          npm install -g @anthropic-ai/claude-code

      - name: Execute Amber agent via Claude Code
        id: amber-execute
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
        run: |
          # Run Claude Code with full tool access (you make the rules!)
          cat amber-prompt.md | claude --print --dangerously-skip-permissions || true
          echo "Claude Code execution completed"

      - name: Check if changes were made
        id: check-changes
        run: |
          # Check if there are any new commits on this branch vs main
          CURRENT_BRANCH=$(git branch --show-current)
          COMMITS_AHEAD=$(git rev-list --count origin/main.."$CURRENT_BRANCH" 2>/dev/null || echo "0")

          if [ "$COMMITS_AHEAD" -eq 0 ]; then
            echo "has_changes=false" >> $GITHUB_OUTPUT
            echo "No changes made by Amber (no new commits)"
          else
            COMMIT_SHA=$(git rev-parse HEAD)
            echo "has_changes=true" >> $GITHUB_OUTPUT
            echo "branch_name=$CURRENT_BRANCH" >> $GITHUB_OUTPUT
            echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT
            echo "Changes committed on branch $CURRENT_BRANCH (commit: ${COMMIT_SHA:0:7})"
            echo "Commits ahead of main: $COMMITS_AHEAD"
          fi

      - name: Report no changes
        if: steps.check-changes.outputs.has_changes == 'false'
        env:
          ISSUE_NUMBER: ${{ steps.issue-details.outputs.issue_number }}
          ACTION_TYPE: ${{ steps.action-type.outputs.type }}
          RUN_ID: ${{ github.run_id }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_REPOSITORY: ${{ github.repository }}
        uses: actions/github-script@v8
        with:
          script: |
            const issueNumber = parseInt(process.env.ISSUE_NUMBER);
            const actionType = process.env.ACTION_TYPE;
            const runId = process.env.RUN_ID;
            const serverUrl = process.env.GITHUB_SERVER_URL;
            const repository = process.env.GITHUB_REPOSITORY;

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: issueNumber,
              body: `✅ Amber reviewed this issue but found no changes were needed.

            **Action Type:** ${actionType}

            **Possible reasons:**
            - Files are already properly formatted
            - No linting issues found
            - The requested changes may have already been applied

            If you believe changes are still needed, please provide more specific instructions or file paths in the issue description.

            ---
            🔍 [View AI decision process](${serverUrl}/${repository}/actions/runs/${runId}) (logs available for 90 days)`
            });

      - name: Push branch to remote
        if: steps.check-changes.outputs.has_changes == 'true'
        env:
          BRANCH_NAME: ${{ steps.check-changes.outputs.branch_name }}
        run: |
          git push -u origin "$BRANCH_NAME"
          echo "Pushed branch $BRANCH_NAME to remote"

      - name: Validate changes align with issue intent
        if: steps.check-changes.outputs.has_changes == 'true'
        env:
          ISSUE_NUMBER: ${{ steps.issue-details.outputs.issue_number }}
          RUN_ID: ${{ github.run_id }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          EXISTING_PR: ${{ steps.check-existing-pr.outputs.existing_pr }}
          EXISTING_PR_NUMBER: ${{ steps.check-existing-pr.outputs.pr_number }}
        uses: actions/github-script@v8
        with:
          script: |
            const { execFile } = require('child_process');
            const { promisify } = require('util');
            const execFileAsync = promisify(execFile);

            const issueNumber = parseInt(process.env.ISSUE_NUMBER);
            const runId = process.env.RUN_ID;
            const serverUrl = process.env.GITHUB_SERVER_URL;
            const repository = process.env.GITHUB_REPOSITORY;
            const existingPr = process.env.EXISTING_PR === 'true';
            const existingPrNumber = process.env.EXISTING_PR_NUMBER;

            // Safely get git diff (no shell injection risk with execFile)
            const { stdout: diff } = await execFileAsync('git', ['diff', 'HEAD~1', '--stat']);

            const nextSteps = existingPr
              ? `- Review that changes match the issue description\n- Verify no scope creep or unintended modifications\n- Changes pushed to existing PR #${existingPrNumber}`
              : `- Review that changes match the issue description\n- Verify no scope creep or unintended modifications\n- A PR will be created shortly for formal review`;

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: issueNumber,
              body: `## Amber Change Summary\n\nThe following files were modified:\n\n\`\`\`\n${diff}\n\`\`\`\n\n**Next Steps:**\n${nextSteps}\n\n---\n🔍 [View AI decision process](${serverUrl}/${repository}/actions/runs/${runId}) (logs available for 90 days)`
            });

      - name: Create or Update Pull Request
        if: steps.check-changes.outputs.has_changes == 'true'
        env:
          BRANCH_NAME: ${{ steps.check-changes.outputs.branch_name }}
          COMMIT_SHA: ${{ steps.check-changes.outputs.commit_sha }}
          ISSUE_NUMBER: ${{ steps.issue-details.outputs.issue_number }}
          ISSUE_TITLE: ${{ steps.issue-details.outputs.issue_title }}
          ACTION_TYPE: ${{ steps.action-type.outputs.type }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          RUN_ID: ${{ github.run_id }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          EXISTING_PR: ${{ steps.check-existing-pr.outputs.existing_pr }}
          EXISTING_PR_NUMBER: ${{ steps.check-existing-pr.outputs.pr_number }}
        uses: actions/github-script@v8
        with:
          script: |
            const branchName = process.env.BRANCH_NAME;
            const commitSha = process.env.COMMIT_SHA;
            const issueNumber = parseInt(process.env.ISSUE_NUMBER);
            const issueTitle = process.env.ISSUE_TITLE;
            const actionType = process.env.ACTION_TYPE;
            const repository = process.env.GITHUB_REPOSITORY;
            const runId = process.env.RUN_ID;
            const serverUrl = process.env.GITHUB_SERVER_URL;
            const existingPr = process.env.EXISTING_PR === 'true';
            const existingPrNumber = process.env.EXISTING_PR_NUMBER ? parseInt(process.env.EXISTING_PR_NUMBER) : null;

            // Helper function for retrying API calls with exponential backoff
            // Retries on: 5xx errors, network errors (no status), JSON parse errors
            async function retryWithBackoff(fn, maxRetries = 3, initialDelay = 1000) {
              for (let i = 0; i < maxRetries; i++) {
                try {
                  return await fn();
                } catch (error) {
                  const isLastAttempt = i === maxRetries - 1;
                  // Retry on: network errors (undefined status), 5xx errors, or specific error patterns
                  const isRetriable = !error.status || error.status >= 500;

                  if (isLastAttempt || !isRetriable) {
                    throw error;
                  }

                  const delay = initialDelay * Math.pow(2, i);
                  const errorMsg = error.message || 'Unknown error';
                  const errorStatus = error.status || 'network error';
                  console.log(`Attempt ${i + 1} failed (${errorStatus}: ${errorMsg}), retrying in ${delay}ms...`);
                  await new Promise(resolve => setTimeout(resolve, delay));
                }
              }
              // Defensive: Should never reach here due to throw in loop, but explicit for clarity
              throw new Error('retryWithBackoff: max retries exceeded');
            }

            // Helper function to safely add a comment with fallback logging
            async function safeComment(issueNum, body, description) {
              try {
                await retryWithBackoff(async () => {
                  return await github.rest.issues.createComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: issueNum,
                    body: body
                  });
                });
                console.log(`Successfully added comment: ${description}`);
              } catch (commentError) {
                // Log but don't fail the workflow for comment failures
                console.log(`Warning: Failed to add comment (${description}): ${commentError.message}`);
                console.log(`Comment body was: ${body.substring(0, 200)}...`);
              }
            }

            try {
              // If PR already exists, just add a comment about the new push
              if (existingPr && existingPrNumber) {
                console.log(`PR #${existingPrNumber} already exists, adding update comment`);

                // Add comment to PR about the new commit (with fallback)
                await safeComment(
                  existingPrNumber,
                  `🤖 **Amber pushed additional changes**

            - **Commit:** ${commitSha.substring(0, 7)}
            - **Action Type:** ${actionType}

            New changes have been pushed to this PR. Please review the updated code.

            ---
            🔍 [View AI decision process](${serverUrl}/${repository}/actions/runs/${runId}) (logs available for 90 days)`,
                  `PR #${existingPrNumber} update notification`
                );

                // Also notify on the issue (with fallback)
                await safeComment(
                  issueNumber,
                  `🤖 Amber pushed additional changes to the existing PR #${existingPrNumber}.\n\n---\n🔍 [View AI decision process](${serverUrl}/${repository}/actions/runs/${runId}) (logs available for 90 days)`,
                  `Issue #${issueNumber} update notification`
                );

                console.log(`Updated existing PR #${existingPrNumber}`);
                return;
              }

              // Create new PR
              const pr = await github.rest.pulls.create({
                owner: context.repo.owner,
                repo: context.repo.repo,
                title: `[Amber] Fix: ${issueTitle}`,
                head: branchName,
                base: 'main',
                body: `## Automated Fix by Amber Agent

            This PR addresses issue #${issueNumber} using the Amber background agent.

            ### Changes Summary
            - **Action Type:** ${actionType}
            - **Commit:** ${commitSha.substring(0, 7)}
            - **Triggered by:** Issue label/command

            ### Pre-merge Checklist
            - [ ] All linters pass
            - [ ] All tests pass
            - [ ] Changes follow project conventions (CLAUDE.md)
            - [ ] No scope creep beyond issue description

            ### Reviewer Notes
            This PR was automatically generated. Please review:
            1. Code quality and adherence to standards
            2. Test coverage for changes
            3. No unintended side effects

            ---
            🤖 Generated with [Amber Background Agent](https://github.com/${repository}/blob/main/docs/amber-automation.md)

            Closes #${issueNumber}`
              });

              // Add labels with retry logic for transient API failures (non-critical)
              try {
                await retryWithBackoff(async () => {
                  return await github.rest.issues.addLabels({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: pr.data.number,
                    labels: ['amber-generated', 'auto-fix', actionType]
                  });
                });
              } catch (labelError) {
                console.log(`Warning: Failed to add labels to PR #${pr.data.number}: ${labelError.message}`);
              }

              // Link PR back to issue (with fallback)
              await safeComment(
                issueNumber,
                `🤖 Amber has created a pull request to address this issue: #${pr.data.number}\n\nThe changes are ready for review. All automated checks will run on the PR.\n\n---\n🔍 [View AI decision process](${serverUrl}/${repository}/actions/runs/${runId}) (logs available for 90 days)`,
                `Issue #${issueNumber} PR link notification`
              );

              console.log('Created PR:', pr.data.html_url);
            } catch (error) {
              console.error('Failed to create/update PR:', error);
              core.setFailed(`PR creation/update failed: ${error.message}`);

              // Notify on issue about failure (with fallback - best effort)
              await safeComment(
                issueNumber,
                `⚠️ Amber completed changes but failed to create a pull request.\n\n**Error:** ${error.message}\n\nChanges committed to \`${branchName}\`. A maintainer can manually create the PR.`,
                `Issue #${issueNumber} PR failure notification`
              );
            }

      - name: Report failure
        if: failure()
        env:
          ISSUE_NUMBER: ${{ steps.issue-details.outputs.issue_number }}
          ACTION_TYPE: ${{ steps.action-type.outputs.type }}
          RUN_ID: ${{ github.run_id }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_REPOSITORY: ${{ github.repository }}
        uses: actions/github-script@v8
        with:
          script: |
            const issueNumber = parseInt(process.env.ISSUE_NUMBER);
            const actionType = process.env.ACTION_TYPE || 'unknown';
            const runId = process.env.RUN_ID;
            const serverUrl = process.env.GITHUB_SERVER_URL;
            const repository = process.env.GITHUB_REPOSITORY;

            // Validate issue number before attempting comment
            if (!issueNumber || isNaN(issueNumber)) {
              console.log('Error: Invalid issue number, cannot post failure comment');
              return;
            }

            try {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                body: `⚠️ Amber encountered an error while processing this issue.

            **Action Type:** ${actionType}
            **Workflow Run:** ${serverUrl}/${repository}/actions/runs/${runId}

            Please review the workflow logs for details. You may need to:
            1. Check if the issue description provides sufficient context
            2. Verify the specified files exist
            3. Ensure the changes are feasible for automation

            Manual intervention may be required for complex changes.`
              });
              console.log(`Posted failure comment to issue #${issueNumber}`);
            } catch (commentError) {
              console.log(`Warning: Failed to post failure comment to issue #${issueNumber}: ${commentError.message}`);
            }

How to Apply

  1. Go to .github/workflows/amber-issue-handler.yml
  2. Click the pencil icon (Edit)
  3. Replace the entire file with the YAML above
  4. Commit directly to main

Verification

After saving, re-run the amber handler on issue #591 (or re-label with amber:auto-fix).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions