diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml
index a73fb1e2a21..3b4d5ca252f 100644
--- a/.github/workflows/github-mcp-tools-report.lock.yml
+++ b/.github/workflows/github-mcp-tools-report.lock.yml
@@ -11,12 +11,15 @@
# agent["agent"]
# detection["detection"]
# create_discussion["create_discussion"]
+# create_pull_request["create_pull_request"]
# missing_tool["missing_tool"]
# pre_activation --> activation
# activation --> agent
# agent --> detection
# agent --> create_discussion
# detection --> create_discussion
+# agent --> create_pull_request
+# detection --> create_pull_request
# agent --> missing_tool
# detection --> missing_tool
# ```
@@ -150,7 +153,7 @@ jobs:
contents: read
env:
GITHUB_AW_SAFE_OUTPUTS: /tmp/gh-aw/safe-outputs/outputs.jsonl
- GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"max\":1},\"missing_tool\":{}}"
+ GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"max\":1},\"create_pull_request\":{},\"missing_tool\":{}}"
outputs:
output: ${{ steps.collect_output.outputs.output }}
output_types: ${{ steps.collect_output.outputs.output_types }}
@@ -354,7 +357,7 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/safe-outputs
cat > /tmp/gh-aw/safe-outputs/config.json << 'EOF'
- {"create_discussion":{"max":1},"missing_tool":{}}
+ {"create_discussion":{"max":1},"create_pull_request":{},"missing_tool":{}}
EOF
cat > /tmp/gh-aw/safe-outputs/mcp-server.cjs << 'EOF'
const fs = require("fs");
@@ -1536,10 +1539,19 @@ jobs:
---
- ## Reporting Missing Tools or Functionality
+ ## Creating a Pull RequestReporting Missing Tools or Functionality
**IMPORTANT**: To do the actions mentioned in the header of this section, use the **safe-outputs** tools, do NOT attempt to use `gh`, do NOT attempt to use the GitHub API. You don't have write access to the GitHub repo.
+ **Creating a Pull Request**
+
+ To create a pull request:
+ 1. Make any file changes directly in the working directory
+ 2. If you haven't done so already, create a local branch using an appropriate unique name
+ 3. Add and commit your changes to the branch. Be careful to add exactly the files you intend, and check there are no extra files left un-added. Check you haven't deleted or changed any files you didn't intend to.
+ 4. Do not push your changes. That will be done by the tool.
+ 5. Create the pull request with the create-pull-request tool from the safe-outputs MCP
+
**Reporting Missing Tools or Functionality**
To report a missing tool use the missing-tool tool from the safe-outputs MCP.
@@ -1676,11 +1688,33 @@ jobs:
- name: Execute Claude Code CLI
id: agentic_execution
# Allowed tools (sorted):
+ # - Bash(cat)
+ # - Bash(date)
+ # - Bash(echo)
+ # - Bash(git add:*)
+ # - Bash(git branch:*)
+ # - Bash(git checkout:*)
+ # - Bash(git commit:*)
+ # - Bash(git merge:*)
+ # - Bash(git rm:*)
+ # - Bash(git status)
+ # - Bash(git switch:*)
+ # - Bash(grep)
+ # - Bash(head)
+ # - Bash(ls)
+ # - Bash(pwd)
+ # - Bash(sort)
+ # - Bash(tail)
+ # - Bash(uniq)
+ # - Bash(wc)
+ # - Bash(yq)
+ # - BashOutput
# - Edit
# - Edit(/tmp/gh-aw/cache-memory/*)
# - ExitPlanMode
# - Glob
# - Grep
+ # - KillBash
# - LS
# - MultiEdit
# - MultiEdit(/tmp/gh-aw/cache-memory/*)
@@ -1750,7 +1784,7 @@ jobs:
run: |
set -o pipefail
# Execute Claude Code CLI with prompt from file
- claude --print --mcp-config /tmp/gh-aw/mcp-config/mcp-servers.json --allowed-tools "Edit,Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,LS,MultiEdit,MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookEdit,NotebookRead,Read,Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/cache-memory/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_sub_issues,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users" --debug --verbose --permission-mode bypassPermissions --output-format stream-json --settings /tmp/gh-aw/.claude/settings.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee /tmp/gh-aw/agent-stdio.log
+ claude --print --mcp-config /tmp/gh-aw/mcp-config/mcp-servers.json --allowed-tools "Bash(cat),Bash(date),Bash(echo),Bash(git add:*),Bash(git branch:*),Bash(git checkout:*),Bash(git commit:*),Bash(git merge:*),Bash(git rm:*),Bash(git status),Bash(git switch:*),Bash(grep),Bash(head),Bash(ls),Bash(pwd),Bash(sort),Bash(tail),Bash(uniq),Bash(wc),Bash(yq),BashOutput,Edit,Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookEdit,NotebookRead,Read,Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/cache-memory/*),mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_sub_issues,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users" --debug --verbose --permission-mode bypassPermissions --output-format stream-json --settings /tmp/gh-aw/.claude/settings.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" 2>&1 | tee /tmp/gh-aw/agent-stdio.log
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
DISABLE_TELEMETRY: "1"
@@ -1781,7 +1815,7 @@ jobs:
uses: actions/github-script@v8
env:
GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
- GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"max\":1},\"missing_tool\":{}}"
+ GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"max\":1},\"create_pull_request\":{},\"missing_tool\":{}}"
with:
script: |
async function main() {
@@ -3169,6 +3203,96 @@ jobs:
if (typeof module === "undefined" || require.main === module) {
main();
}
+ - name: Generate git patch
+ if: always()
+ env:
+ GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}
+ GITHUB_SHA: ${{ github.sha }}
+ run: |
+ # Check current git status
+ echo "Current git status:"
+ git status
+ # Extract branch name from JSONL output
+ BRANCH_NAME=""
+ if [ -f "$GITHUB_AW_SAFE_OUTPUTS" ]; then
+ echo "Checking for branch name in JSONL output..."
+ while IFS= read -r line; do
+ if [ -n "$line" ]; then
+ # Extract branch from create-pull-request line using simple grep and sed
+ # Note: types use underscores (normalized by safe-outputs MCP server)
+ if echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"create_pull_request"'; then
+ echo "Found create_pull_request line: $line"
+ # Extract branch value using sed
+ BRANCH_NAME=$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
+ if [ -n "$BRANCH_NAME" ]; then
+ echo "Extracted branch name from create_pull_request: $BRANCH_NAME"
+ break
+ fi
+ # Extract branch from push_to_pull_request_branch line using simple grep and sed
+ # Note: types use underscores (normalized by safe-outputs MCP server)
+ elif echo "$line" | grep -q '"type"[[:space:]]*:[[:space:]]*"push_to_pull_request_branch"'; then
+ echo "Found push_to_pull_request_branch line: $line"
+ # Extract branch value using sed
+ BRANCH_NAME=$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
+ if [ -n "$BRANCH_NAME" ]; then
+ echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME"
+ break
+ fi
+ fi
+ fi
+ done < "$GITHUB_AW_SAFE_OUTPUTS"
+ fi
+ # If no branch or branch doesn't exist, no patch
+ if [ -z "$BRANCH_NAME" ]; then
+ echo "No branch found, no patch generation"
+ fi
+ # If we have a branch name, check if that branch exists and get its diff
+ if [ -n "$BRANCH_NAME" ]; then
+ echo "Looking for branch: $BRANCH_NAME"
+ # Check if the branch exists
+ if git show-ref --verify --quiet refs/heads/$BRANCH_NAME; then
+ echo "Branch $BRANCH_NAME exists, generating patch from branch changes"
+ # Check if origin/$BRANCH_NAME exists to use as base
+ if git show-ref --verify --quiet refs/remotes/origin/$BRANCH_NAME; then
+ echo "Using origin/$BRANCH_NAME as base for patch generation"
+ BASE_REF="origin/$BRANCH_NAME"
+ else
+ echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch"
+ # Get the default branch name
+ DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
+ echo "Default branch: $DEFAULT_BRANCH"
+ # Fetch the default branch to ensure it's available locally
+ git fetch origin $DEFAULT_BRANCH
+ # Find merge base between default branch and current branch
+ BASE_REF=$(git merge-base origin/$DEFAULT_BRANCH $BRANCH_NAME)
+ echo "Using merge-base as base: $BASE_REF"
+ fi
+ # Generate patch from the determined base to the branch
+ 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)"
+ else
+ echo "Branch $BRANCH_NAME does not exist, no patch"
+ fi
+ fi
+ # Show patch info if it exists
+ if [ -f /tmp/gh-aw/aw.patch ]; then
+ ls -la /tmp/gh-aw/aw.patch
+ # Show the first 50 lines of the patch for review
+ echo '## Git Patch' >> $GITHUB_STEP_SUMMARY
+ echo '' >> $GITHUB_STEP_SUMMARY
+ echo '```diff' >> $GITHUB_STEP_SUMMARY
+ head -500 /tmp/gh-aw/aw.patch >> $GITHUB_STEP_SUMMARY || echo "Could not display patch contents" >> $GITHUB_STEP_SUMMARY
+ echo '...' >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo '' >> $GITHUB_STEP_SUMMARY
+ fi
+ - name: Upload git patch
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: aw.patch
+ path: /tmp/gh-aw/aw.patch
+ if-no-files-found: ignore
detection:
needs: agent
@@ -3615,6 +3739,447 @@ jobs:
}
await main();
+ create_pull_request:
+ needs:
+ - agent
+ - detection
+ if: (always()) && (contains(needs.agent.outputs.output_types, 'create_pull_request'))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+ timeout-minutes: 10
+ outputs:
+ branch_name: ${{ steps.create_pull_request.outputs.branch_name }}
+ fallback_used: ${{ steps.create_pull_request.outputs.fallback_used }}
+ issue_number: ${{ steps.create_pull_request.outputs.issue_number }}
+ issue_url: ${{ steps.create_pull_request.outputs.issue_url }}
+ pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }}
+ pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }}
+ steps:
+ - name: Download patch artifact
+ continue-on-error: true
+ uses: actions/download-artifact@v5
+ with:
+ name: aw.patch
+ path: /tmp/gh-aw/
+ - name: Checkout repository
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+ - name: Configure Git credentials
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "${{ github.workflow }}"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Create Pull Request
+ id: create_pull_request
+ uses: actions/github-script@v8
+ env:
+ GITHUB_AW_AGENT_OUTPUT: ${{ needs.agent.outputs.output }}
+ GITHUB_AW_WORKFLOW_ID: "agent"
+ GITHUB_AW_WORKFLOW_NAME: "GitHub MCP Remote Server Tools Report Generator"
+ GITHUB_AW_BASE_BRANCH: ${{ github.ref_name }}
+ GITHUB_AW_PR_TITLE_PREFIX: "[mcp-tools] "
+ GITHUB_AW_PR_LABELS: "documentation,automation"
+ GITHUB_AW_PR_DRAFT: "false"
+ GITHUB_AW_PR_IF_NO_CHANGES: "warn"
+ GITHUB_AW_MAX_PATCH_SIZE: 1024
+ with:
+ script: |
+ const fs = require("fs");
+ const crypto = require("crypto");
+ function generatePatchPreview(patchContent) {
+ if (!patchContent || !patchContent.trim()) {
+ return "";
+ }
+ const lines = patchContent.split("\n");
+ const maxLines = 500;
+ const maxChars = 2000;
+ let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n");
+ const lineTruncated = lines.length > maxLines;
+ const charTruncated = preview.length > maxChars;
+ if (charTruncated) {
+ preview = preview.slice(0, maxChars);
+ }
+ const truncated = lineTruncated || charTruncated;
+ const summary = truncated
+ ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)`
+ : `Show patch (${lines.length} lines)`;
+ return `\n\n${summary}
\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n `;
+ }
+ async function main() {
+ const isStaged = process.env.GITHUB_AW_SAFE_OUTPUTS_STAGED === "true";
+ const workflowId = process.env.GITHUB_AW_WORKFLOW_ID;
+ if (!workflowId) {
+ throw new Error("GITHUB_AW_WORKFLOW_ID environment variable is required");
+ }
+ const baseBranch = process.env.GITHUB_AW_BASE_BRANCH;
+ if (!baseBranch) {
+ throw new Error("GITHUB_AW_BASE_BRANCH environment variable is required");
+ }
+ const outputContent = process.env.GITHUB_AW_AGENT_OUTPUT || "";
+ if (outputContent.trim() === "") {
+ core.info("Agent output content is empty");
+ }
+ const ifNoChanges = process.env.GITHUB_AW_PR_IF_NO_CHANGES || "warn";
+ if (!fs.existsSync("/tmp/gh-aw/aw.patch")) {
+ const message = "No patch file found - cannot create pull request without changes";
+ if (isStaged) {
+ let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n";
+ summaryContent += "The following pull request would be created if staged mode was disabled:\n\n";
+ summaryContent += `**Status:** ⚠️ No patch file found\n\n`;
+ summaryContent += `**Message:** ${message}\n\n`;
+ await core.summary.addRaw(summaryContent).write();
+ core.info("📝 Pull request creation preview written to step summary (no patch file)");
+ return;
+ }
+ switch (ifNoChanges) {
+ case "error":
+ throw new Error(message);
+ case "ignore":
+ return;
+ case "warn":
+ default:
+ core.warning(message);
+ return;
+ }
+ }
+ const patchContent = fs.readFileSync("/tmp/gh-aw/aw.patch", "utf8");
+ if (patchContent.includes("Failed to generate patch")) {
+ const message = "Patch file contains error message - cannot create pull request without changes";
+ if (isStaged) {
+ let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n";
+ summaryContent += "The following pull request would be created if staged mode was disabled:\n\n";
+ summaryContent += `**Status:** ⚠️ Patch file contains error\n\n`;
+ summaryContent += `**Message:** ${message}\n\n`;
+ await core.summary.addRaw(summaryContent).write();
+ core.info("📝 Pull request creation preview written to step summary (patch error)");
+ return;
+ }
+ switch (ifNoChanges) {
+ case "error":
+ throw new Error(message);
+ case "ignore":
+ return;
+ case "warn":
+ default:
+ core.warning(message);
+ return;
+ }
+ }
+ const isEmpty = !patchContent || !patchContent.trim();
+ if (!isEmpty) {
+ const maxSizeKb = parseInt(process.env.GITHUB_AW_MAX_PATCH_SIZE || "1024", 10);
+ const patchSizeBytes = Buffer.byteLength(patchContent, "utf8");
+ const patchSizeKb = Math.ceil(patchSizeBytes / 1024);
+ core.info(`Patch size: ${patchSizeKb} KB (maximum allowed: ${maxSizeKb} KB)`);
+ if (patchSizeKb > maxSizeKb) {
+ const message = `Patch size (${patchSizeKb} KB) exceeds maximum allowed size (${maxSizeKb} KB)`;
+ if (isStaged) {
+ let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n";
+ summaryContent += "The following pull request would be created if staged mode was disabled:\n\n";
+ summaryContent += `**Status:** ❌ Patch size exceeded\n\n`;
+ summaryContent += `**Message:** ${message}\n\n`;
+ await core.summary.addRaw(summaryContent).write();
+ core.info("📝 Pull request creation preview written to step summary (patch size error)");
+ return;
+ }
+ throw new Error(message);
+ }
+ core.info("Patch size validation passed");
+ }
+ if (isEmpty && !isStaged) {
+ const message = "Patch file is empty - no changes to apply (noop operation)";
+ switch (ifNoChanges) {
+ case "error":
+ throw new Error("No changes to push - failing as configured by if-no-changes: error");
+ case "ignore":
+ return;
+ case "warn":
+ default:
+ core.warning(message);
+ return;
+ }
+ }
+ core.debug(`Agent output content length: ${outputContent.length}`);
+ if (!isEmpty) {
+ core.info("Patch content validation passed");
+ } else {
+ core.info("Patch file is empty - processing noop operation");
+ }
+ let validatedOutput;
+ try {
+ validatedOutput = JSON.parse(outputContent);
+ } catch (error) {
+ core.setFailed(`Error parsing agent output JSON: ${error instanceof Error ? error.message : String(error)}`);
+ return;
+ }
+ if (!validatedOutput.items || !Array.isArray(validatedOutput.items)) {
+ core.warning("No valid items found in agent output");
+ return;
+ }
+ const pullRequestItem = validatedOutput.items.find( item => item.type === "create_pull_request");
+ if (!pullRequestItem) {
+ core.warning("No create-pull-request item found in agent output");
+ return;
+ }
+ core.debug(`Found create-pull-request item: title="${pullRequestItem.title}", bodyLength=${pullRequestItem.body.length}`);
+ if (isStaged) {
+ let summaryContent = "## 🎭 Staged Mode: Create Pull Request Preview\n\n";
+ summaryContent += "The following pull request would be created if staged mode was disabled:\n\n";
+ summaryContent += `**Title:** ${pullRequestItem.title || "No title provided"}\n\n`;
+ summaryContent += `**Branch:** ${pullRequestItem.branch || "auto-generated"}\n\n`;
+ summaryContent += `**Base:** ${baseBranch}\n\n`;
+ if (pullRequestItem.body) {
+ summaryContent += `**Body:**\n${pullRequestItem.body}\n\n`;
+ }
+ if (fs.existsSync("/tmp/gh-aw/aw.patch")) {
+ const patchStats = fs.readFileSync("/tmp/gh-aw/aw.patch", "utf8");
+ if (patchStats.trim()) {
+ summaryContent += `**Changes:** Patch file exists with ${patchStats.split("\n").length} lines\n\n`;
+ summaryContent += `Show patch preview
\n\n\`\`\`diff\n${patchStats.slice(0, 2000)}${patchStats.length > 2000 ? "\n... (truncated)" : ""}\n\`\`\`\n\n \n\n`;
+ } else {
+ summaryContent += `**Changes:** No changes (empty patch)\n\n`;
+ }
+ }
+ await core.summary.addRaw(summaryContent).write();
+ core.info("📝 Pull request creation preview written to step summary");
+ return;
+ }
+ let title = pullRequestItem.title.trim();
+ let bodyLines = pullRequestItem.body.split("\n");
+ let branchName = pullRequestItem.branch ? pullRequestItem.branch.trim() : null;
+ if (!title) {
+ title = "Agent Output";
+ }
+ const titlePrefix = process.env.GITHUB_AW_PR_TITLE_PREFIX;
+ if (titlePrefix && !title.startsWith(titlePrefix)) {
+ title = titlePrefix + title;
+ }
+ const workflowName = process.env.GITHUB_AW_WORKFLOW_NAME || "Workflow";
+ const runId = context.runId;
+ const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com";
+ const runUrl = context.payload.repository
+ ? `${context.payload.repository.html_url}/actions/runs/${runId}`
+ : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
+ bodyLines.push(``, ``, `> AI generated by [${workflowName}](${runUrl})`, "");
+ const body = bodyLines.join("\n").trim();
+ const labelsEnv = process.env.GITHUB_AW_PR_LABELS;
+ const labels = labelsEnv
+ ? labelsEnv
+ .split(",")
+ .map( label => label.trim())
+ .filter( label => label)
+ : [];
+ const draftEnv = process.env.GITHUB_AW_PR_DRAFT;
+ const draft = draftEnv ? draftEnv.toLowerCase() === "true" : true;
+ core.info(`Creating pull request with title: ${title}`);
+ core.debug(`Labels: ${JSON.stringify(labels)}`);
+ core.debug(`Draft: ${draft}`);
+ core.debug(`Body length: ${body.length}`);
+ const randomHex = crypto.randomBytes(8).toString("hex");
+ if (!branchName) {
+ core.debug("No branch name provided in JSONL, generating unique branch name");
+ branchName = `${workflowId}-${randomHex}`;
+ } else {
+ branchName = `${branchName}-${randomHex}`;
+ core.debug(`Using branch name from JSONL with added salt: ${branchName}`);
+ }
+ core.info(`Generated branch name: ${branchName}`);
+ core.debug(`Base branch: ${baseBranch}`);
+ core.debug(`Fetching latest changes and checking out base branch: ${baseBranch}`);
+ await exec.exec("git fetch origin");
+ await exec.exec(`git checkout ${baseBranch}`);
+ core.debug(`Branch should not exist locally, creating new branch from base: ${branchName}`);
+ await exec.exec(`git checkout -b ${branchName}`);
+ core.info(`Created new branch from base: ${branchName}`);
+ if (!isEmpty) {
+ core.info("Applying patch...");
+ await exec.exec("git am /tmp/gh-aw/aw.patch");
+ core.info("Patch applied successfully");
+ try {
+ let remoteBranchExists = false;
+ try {
+ const { stdout } = await exec.getExecOutput(`git ls-remote --heads origin ${branchName}`);
+ if (stdout.trim()) {
+ remoteBranchExists = true;
+ }
+ } catch (checkError) {
+ core.debug(`Remote branch check failed (non-fatal): ${checkError instanceof Error ? checkError.message : String(checkError)}`);
+ }
+ if (remoteBranchExists) {
+ core.warning(`Remote branch ${branchName} already exists - appending random suffix`);
+ const extraHex = crypto.randomBytes(4).toString("hex");
+ const oldBranch = branchName;
+ branchName = `${branchName}-${extraHex}`;
+ await exec.exec(`git branch -m ${oldBranch} ${branchName}`);
+ core.info(`Renamed branch to ${branchName}`);
+ }
+ await exec.exec(`git push origin ${branchName}`);
+ core.info("Changes pushed to branch");
+ } catch (pushError) {
+ core.error(`Git push failed: ${pushError instanceof Error ? pushError.message : String(pushError)}`);
+ core.warning("Git push operation failed - creating fallback issue instead of pull request");
+ const runId = context.runId;
+ const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com";
+ const runUrl = context.payload.repository
+ ? `${context.payload.repository.html_url}/actions/runs/${runId}`
+ : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
+ let patchPreview = "";
+ if (fs.existsSync("/tmp/gh-aw/aw.patch")) {
+ const patchContent = fs.readFileSync("/tmp/gh-aw/aw.patch", "utf8");
+ patchPreview = generatePatchPreview(patchContent);
+ }
+ const fallbackBody = `${body}
+ ---
+ > [!NOTE]
+ > This was originally intended as a pull request, but the git push operation failed.
+ >
+ > **Workflow Run:** [View run details and download patch artifact](${runUrl})
+ >
+ > The patch file is available as an artifact (\`aw.patch\`) in the workflow run linked above.
+ To apply the patch locally:
+ \`\`\`sh
+ # Download the artifact from the workflow run ${runUrl}
+ # (Use GitHub MCP tools if gh CLI is not available)
+ gh run download ${runId} -n aw.patch
+ # Apply the patch
+ git am aw.patch
+ \`\`\`
+ ${patchPreview}`;
+ try {
+ const { data: issue } = await github.rest.issues.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: title,
+ body: fallbackBody,
+ labels: labels,
+ });
+ core.info(`Created fallback issue #${issue.number}: ${issue.html_url}`);
+ core.setOutput("issue_number", issue.number);
+ core.setOutput("issue_url", issue.html_url);
+ core.setOutput("branch_name", branchName);
+ core.setOutput("fallback_used", "true");
+ core.setOutput("push_failed", "true");
+ await core.summary
+ .addRaw(
+ `
+ ## Push Failure Fallback
+ - **Push Error:** ${pushError instanceof Error ? pushError.message : String(pushError)}
+ - **Fallback Issue:** [#${issue.number}](${issue.html_url})
+ - **Patch Artifact:** Available in workflow run artifacts
+ - **Note:** Push failed, created issue as fallback
+ `
+ )
+ .write();
+ return;
+ } catch (issueError) {
+ core.setFailed(
+ `Failed to push and failed to create fallback issue. Push error: ${pushError instanceof Error ? pushError.message : String(pushError)}. Issue error: ${issueError instanceof Error ? issueError.message : String(issueError)}`
+ );
+ return;
+ }
+ }
+ } else {
+ core.info("Skipping patch application (empty patch)");
+ const message = "No changes to apply - noop operation completed successfully";
+ switch (ifNoChanges) {
+ case "error":
+ throw new Error("No changes to apply - failing as configured by if-no-changes: error");
+ case "ignore":
+ return;
+ case "warn":
+ default:
+ core.warning(message);
+ return;
+ }
+ }
+ try {
+ const { data: pullRequest } = await github.rest.pulls.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: title,
+ body: body,
+ head: branchName,
+ base: baseBranch,
+ draft: draft,
+ });
+ core.info(`Created pull request #${pullRequest.number}: ${pullRequest.html_url}`);
+ if (labels.length > 0) {
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: pullRequest.number,
+ labels: labels,
+ });
+ core.info(`Added labels to pull request: ${JSON.stringify(labels)}`);
+ }
+ core.setOutput("pull_request_number", pullRequest.number);
+ core.setOutput("pull_request_url", pullRequest.html_url);
+ core.setOutput("branch_name", branchName);
+ await core.summary
+ .addRaw(
+ `
+ ## Pull Request
+ - **Pull Request**: [#${pullRequest.number}](${pullRequest.html_url})
+ - **Branch**: \`${branchName}\`
+ - **Base Branch**: \`${baseBranch}\`
+ `
+ )
+ .write();
+ } catch (prError) {
+ core.warning(`Failed to create pull request: ${prError instanceof Error ? prError.message : String(prError)}`);
+ core.info("Falling back to creating an issue instead");
+ const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com";
+ const branchUrl = context.payload.repository
+ ? `${context.payload.repository.html_url}/tree/${branchName}`
+ : `${githubServer}/${context.repo.owner}/${context.repo.repo}/tree/${branchName}`;
+ let patchPreview = "";
+ if (fs.existsSync("/tmp/gh-aw/aw.patch")) {
+ const patchContent = fs.readFileSync("/tmp/gh-aw/aw.patch", "utf8");
+ patchPreview = generatePatchPreview(patchContent);
+ }
+ const fallbackBody = `${body}
+ ---
+ **Note:** This was originally intended as a pull request, but PR creation failed. The changes have been pushed to the branch [\`${branchName}\`](${branchUrl}).
+ **Original error:** ${prError instanceof Error ? prError.message : String(prError)}
+ You can manually create a pull request from the branch if needed.${patchPreview}`;
+ try {
+ const { data: issue } = await github.rest.issues.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: title,
+ body: fallbackBody,
+ labels: labels,
+ });
+ core.info(`Created fallback issue #${issue.number}: ${issue.html_url}`);
+ core.setOutput("issue_number", issue.number);
+ core.setOutput("issue_url", issue.html_url);
+ core.setOutput("branch_name", branchName);
+ core.setOutput("fallback_used", "true");
+ await core.summary
+ .addRaw(
+ `
+ ## Fallback Issue Created
+ - **Issue**: [#${issue.number}](${issue.html_url})
+ - **Branch**: [\`${branchName}\`](${branchUrl})
+ - **Base Branch**: \`${baseBranch}\`
+ - **Note**: Pull request creation failed, created issue as fallback
+ `
+ )
+ .write();
+ } catch (issueError) {
+ core.setFailed(
+ `Failed to create both pull request and fallback issue. PR error: ${prError instanceof Error ? prError.message : String(prError)}. Issue error: ${issueError instanceof Error ? issueError.message : String(issueError)}`
+ );
+ return;
+ }
+ }
+ }
+ await main();
+
missing_tool:
needs:
- agent
diff --git a/.github/workflows/github-mcp-tools-report.md b/.github/workflows/github-mcp-tools-report.md
index 6b52e330429..75482868c4f 100644
--- a/.github/workflows/github-mcp-tools-report.md
+++ b/.github/workflows/github-mcp-tools-report.md
@@ -17,6 +17,10 @@ safe-outputs:
create-discussion:
category: "audits"
max: 1
+ create-pull-request:
+ title-prefix: "[mcp-tools] "
+ labels: [documentation, automation]
+ draft: false
timeout_minutes: 15
---