From 54d9385f5996cb1bf4ddb7ab3ecfbba53fb81571 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 22:11:41 +0000 Subject: [PATCH 1/3] feat: add branch-prefix support to create-pull-request safe-outputs, set jsweep to signed/ Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/jsweep.lock.yml | 50 +++++----- .github/workflows/jsweep.md | 1 + actions/setup/js/create_pull_request.cjs | 7 ++ actions/setup/js/create_pull_request.test.cjs | 99 +++++++++++++++++++ .../docs/reference/frontmatter-full.md | 6 ++ pkg/parser/schemas/main_workflow_schema.json | 4 + .../compiler_safe_outputs_handlers.go | 1 + pkg/workflow/create_pull_request.go | 1 + pkg/workflow/jsweep_workflow_test.go | 7 ++ pkg/workflow/tool_description_enhancer.go | 3 + 10 files changed, 154 insertions(+), 25 deletions(-) diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index 0d0eac5b2c3..00738479c24 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_GRAFANA_HEADERS","GH_AW_OTEL_SENTRY_ENDPOINT","GH_AW_OTEL_SENTRY_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -121,7 +121,7 @@ jobs: GH_AW_SETUP_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/jsweep.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.43" - GH_AW_INFO_FRONTMATTER_HASH: "a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e" + GH_AW_INFO_FRONTMATTER_HASH: "abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e" - name: Mask OTLP telemetry headers run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" - name: Generate agentic run info @@ -142,7 +142,7 @@ jobs: GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_INFO_FRONTMATTER_EMOJI: "🧹" - GH_AW_INFO_FRONTMATTER_HASH: "a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e" + GH_AW_INFO_FRONTMATTER_HASH: "abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e" GH_AW_COMPILED_STRICT: "true" uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: @@ -206,24 +206,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_4cf059708627a00c_EOF' + cat << 'GH_AW_PROMPT_16b81d2a1c17dac1_EOF' - GH_AW_PROMPT_4cf059708627a00c_EOF + GH_AW_PROMPT_16b81d2a1c17dac1_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_4cf059708627a00c_EOF' + cat << 'GH_AW_PROMPT_16b81d2a1c17dac1_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_4cf059708627a00c_EOF + GH_AW_PROMPT_16b81d2a1c17dac1_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_4cf059708627a00c_EOF' + cat << 'GH_AW_PROMPT_16b81d2a1c17dac1_EOF' - GH_AW_PROMPT_4cf059708627a00c_EOF + GH_AW_PROMPT_16b81d2a1c17dac1_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_4cf059708627a00c_EOF' + cat << 'GH_AW_PROMPT_16b81d2a1c17dac1_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -252,14 +252,14 @@ jobs: {{/if}} - GH_AW_PROMPT_4cf059708627a00c_EOF + GH_AW_PROMPT_16b81d2a1c17dac1_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_4cf059708627a00c_EOF' + cat << 'GH_AW_PROMPT_16b81d2a1c17dac1_EOF' {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/jsweep.md}} - GH_AW_PROMPT_4cf059708627a00c_EOF + GH_AW_PROMPT_16b81d2a1c17dac1_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -392,7 +392,7 @@ jobs: GH_AW_SETUP_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/jsweep.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.43" - GH_AW_INFO_FRONTMATTER_HASH: "a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e" + GH_AW_INFO_FRONTMATTER_HASH: "abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e" - name: Set runtime paths id: set-runtime-paths run: | @@ -503,15 +503,15 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_da7c000fed66086a_EOF' - {"create_pull_request":{"draft":true,"expires":48,"if_no_changes":"ignore","labels":["unbloat","automation"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"title_prefix":"[jsweep] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_da7c000fed66086a_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7b3e61bb494b60bb_EOF' + {"create_pull_request":{"branch_prefix":"signed/","draft":true,"expires":48,"if_no_changes":"ignore","labels":["unbloat","automation"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"title_prefix":"[jsweep] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_7b3e61bb494b60bb_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[jsweep] \". Labels [\"unbloat\" \"automation\"] will be automatically added. PRs will be created as drafts." + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Branch name will be prefixed with \"signed/\". Title will be prefixed with \"[jsweep] \". Labels [\"unbloat\" \"automation\"] will be automatically added. PRs will be created as drafts." }, "repo_params": {}, "dynamic_tools": [] @@ -717,7 +717,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_a0c9b93460be3dd2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_968d28bab68c3c61_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -748,7 +748,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_a0c9b93460be3dd2_EOF + GH_AW_MCP_CONFIG_968d28bab68c3c61_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1052,7 +1052,7 @@ jobs: GH_AW_SETUP_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/jsweep.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.43" - GH_AW_INFO_FRONTMATTER_HASH: "a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e" + GH_AW_INFO_FRONTMATTER_HASH: "abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1206,7 +1206,7 @@ jobs: GH_AW_SETUP_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/jsweep.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.43" - GH_AW_INFO_FRONTMATTER_HASH: "a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e" + GH_AW_INFO_FRONTMATTER_HASH: "abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1432,7 +1432,7 @@ jobs: GH_AW_SETUP_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/jsweep.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.43" - GH_AW_INFO_FRONTMATTER_HASH: "a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e" + GH_AW_INFO_FRONTMATTER_HASH: "abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e" - name: Mask OTLP telemetry headers run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" - name: Download agent output artifact @@ -1517,7 +1517,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,bun.sh,cdn.jsdelivr.net,deb.nodesource.com,deno.land,esm.sh,get.pnpm.io,github.com,go.dev,golang.org,googleapis.deno.dev,googlechromelabs.github.io,goproxy.io,host.docker.internal,jsr.io,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,pkg.go.dev,proxy.golang.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,skimdb.npmjs.com,storage.googleapis.com,sum.golang.org,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,www.npmjs.com,www.npmjs.org,yarnpkg.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":true,\"expires\":48,\"if_no_changes\":\"ignore\",\"labels\":[\"unbloat\",\"automation\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"title_prefix\":\"[jsweep] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"branch_prefix\":\"signed/\",\"draft\":true,\"expires\":48,\"if_no_changes\":\"ignore\",\"labels\":[\"unbloat\",\"automation\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"title_prefix\":\"[jsweep] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1578,7 +1578,7 @@ jobs: GH_AW_SETUP_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/jsweep.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.43" - GH_AW_INFO_FRONTMATTER_HASH: "a801fe70c42a1cd2de7dca3c27d0bb73d2df984a7de89e790aa64866578ac17e" + GH_AW_INFO_FRONTMATTER_HASH: "abbcb13a22a54fb4b746e199fa6d7a2d9beb16db5ef1d258ca9fdf319bca3b1e" - name: Download cache-memory artifact (default) id: download_cache_default uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/jsweep.md b/.github/workflows/jsweep.md index 118ed9b5774..0537a7c3d62 100644 --- a/.github/workflows/jsweep.md +++ b/.github/workflows/jsweep.md @@ -30,6 +30,7 @@ safe-outputs: create-pull-request: expires: 2d title-prefix: "[jsweep] " + branch-prefix: "signed/" labels: [unbloat, automation] draft: true if-no-changes: "ignore" diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index 5dc2d97a1dc..ed2a11fdba4 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -614,6 +614,7 @@ async function handleRemoteBranchCollision(branchName, preserveBranchName, optio */ async function main(config = {}) { // Extract configuration + const branchPrefix = config.branch_prefix || ""; const titlePrefix = config.title_prefix || ""; const envLabels = parseStringListConfig(config.labels); const configFallbackLabels = parseStringListConfig(config.fallback_labels); @@ -1313,6 +1314,12 @@ async function main(config = {}) { branchName = `${workflowId}-${randomHex}`; } + // Apply the configured branch prefix (e.g. "signed/") if it hasn't already been applied. + if (branchPrefix && !branchName.startsWith(branchPrefix)) { + branchName = `${branchPrefix}${branchName}`; + core.info(`Applied branch prefix: ${branchName}`); + } + core.info(`Generated branch name: ${branchName}`); core.info(`Base branch: ${baseBranch}`); diff --git a/actions/setup/js/create_pull_request.test.cjs b/actions/setup/js/create_pull_request.test.cjs index 43bf942063a..a1ed94045da 100644 --- a/actions/setup/js/create_pull_request.test.cjs +++ b/actions/setup/js/create_pull_request.test.cjs @@ -2596,3 +2596,102 @@ describe("create_pull_request - rate-limit retry", () => { expect(secondCall.body).toContain("could not be set"); }); }); + +describe("create_pull_request - branch-prefix config", () => { + let originalEnv; + let tempDir; + + beforeEach(() => { + originalEnv = { ...process.env }; + process.env.GH_AW_WORKFLOW_ID = "jsweep"; + process.env.GITHUB_REPOSITORY = "test-owner/test-repo"; + process.env.GITHUB_BASE_REF = "main"; + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-pr-branch-prefix-test-")); + + global.core = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + setFailed: vi.fn(), + setOutput: vi.fn(), + startGroup: vi.fn(), + endGroup: vi.fn(), + summary: { addRaw: vi.fn().mockReturnThis(), write: vi.fn().mockResolvedValue(undefined) }, + }; + global.github = { + rest: { + pulls: { create: vi.fn().mockResolvedValue({ data: { number: 1, html_url: "https://github.com/test/pull/1" } }) }, + repos: { get: vi.fn().mockResolvedValue({ data: { default_branch: "main" } }) }, + issues: { addLabels: vi.fn().mockResolvedValue({}) }, + }, + graphql: vi.fn(), + }; + global.context = { + eventName: "workflow_dispatch", + repo: { owner: "test-owner", repo: "test-repo" }, + payload: {}, + }; + global.exec = { + exec: vi.fn().mockResolvedValue(0), + getExecOutput: vi.fn().mockResolvedValue({ exitCode: 0, stdout: "", stderr: "" }), + }; + + delete require.cache[require.resolve("./create_pull_request.cjs")]; + }); + + afterEach(() => { + for (const key of Object.keys(process.env)) { + if (!(key in originalEnv)) delete process.env[key]; + } + Object.assign(process.env, originalEnv); + if (tempDir && fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true }); + delete global.core; + delete global.github; + delete global.context; + delete global.exec; + vi.clearAllMocks(); + }); + + it("should prepend branch-prefix to auto-generated branch name when agent provides no branch", async () => { + const { main } = require("./create_pull_request.cjs"); + const handler = await main({ branch_prefix: "signed/", allow_empty: true }); + + await handler({ title: "Test PR", body: "body" }, {}); + + const branchArg = global.github.rest.pulls.create.mock.calls[0][0].head; + expect(branchArg).toMatch(/^signed\/jsweep-/); + }); + + it("should prepend branch-prefix to agent-specified branch name", async () => { + const { main } = require("./create_pull_request.cjs"); + const handler = await main({ branch_prefix: "signed/", allow_empty: true }); + + await handler({ title: "Test PR", body: "body", branch: "my-feature" }, {}); + + const branchArg = global.github.rest.pulls.create.mock.calls[0][0].head; + expect(branchArg).toMatch(/^signed\/my-feature/); + }); + + it("should not double-apply branch-prefix when agent branch already starts with the prefix", async () => { + const { main } = require("./create_pull_request.cjs"); + const handler = await main({ branch_prefix: "signed/", allow_empty: true }); + + await handler({ title: "Test PR", body: "body", branch: "signed/already-prefixed" }, {}); + + const branchArg = global.github.rest.pulls.create.mock.calls[0][0].head; + expect(branchArg).toMatch(/^signed\/already-prefixed/); + expect(branchArg).not.toMatch(/^signed\/signed\//); + }); + + it("should not add any prefix when branch-prefix is not configured", async () => { + const { main } = require("./create_pull_request.cjs"); + const handler = await main({ allow_empty: true }); + + await handler({ title: "Test PR", body: "body" }, {}); + + const branchArg = global.github.rest.pulls.create.mock.calls[0][0].head; + expect(branchArg).toMatch(/^jsweep-/); + expect(branchArg).not.toContain("signed/"); + }); +}); diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 11b80b671d6..935e3e1489f 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -4165,6 +4165,12 @@ safe-outputs: # Format 2: GitHub Actions expression that resolves to an integer at runtime max: "example-value" + # Optional prefix to prepend to the pull request branch name (e.g. "signed/"). + # Applied before the agent-specified or auto-generated branch name. When set, any + # branch name that does not already start with this prefix will have it prepended. + # (optional) + branch-prefix: "example-value" + # Optional prefix for the pull request title # (optional) title-prefix: "example-value" diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index a84bc56a829..3aa21bcb570 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -6102,6 +6102,10 @@ } ] }, + "branch-prefix": { + "type": "string", + "description": "Optional prefix to prepend to the pull request branch name (e.g. \"signed/\"). Applied before the agent-specified or auto-generated branch name." + }, "title-prefix": { "type": "string", "description": "Optional prefix for the pull request title" diff --git a/pkg/workflow/compiler_safe_outputs_handlers.go b/pkg/workflow/compiler_safe_outputs_handlers.go index 74be7eaabb7..443617bf080 100644 --- a/pkg/workflow/compiler_safe_outputs_handlers.go +++ b/pkg/workflow/compiler_safe_outputs_handlers.go @@ -389,6 +389,7 @@ var handlerRegistry = map[string]handlerBuilder{ } builder := newHandlerConfigBuilder(). AddTemplatableInt("max", c.Max). + AddIfNotEmpty("branch_prefix", c.BranchPrefix). AddIfNotEmpty("title_prefix", c.TitlePrefix). AddTemplatableStringSlice("labels", c.Labels). AddStringSlice("fallback_labels", c.FallbackLabels). diff --git a/pkg/workflow/create_pull_request.go b/pkg/workflow/create_pull_request.go index bee4586494e..df2df42964f 100644 --- a/pkg/workflow/create_pull_request.go +++ b/pkg/workflow/create_pull_request.go @@ -17,6 +17,7 @@ func getFallbackAsIssue(config *CreatePullRequestsConfig) bool { // CreatePullRequestsConfig holds configuration for creating GitHub pull requests from agent output type CreatePullRequestsConfig struct { BaseSafeOutputConfig `yaml:",inline"` + BranchPrefix string `yaml:"branch-prefix,omitempty"` // Optional prefix for the pull request branch name (e.g. "signed/"). Applied before the agent-specified or auto-generated branch name. TitlePrefix string `yaml:"title-prefix,omitempty"` Labels []string `yaml:"labels,omitempty"` AllowedLabels []string `yaml:"allowed-labels,omitempty"` // Optional list of allowed labels. If omitted, any labels are allowed (including creating new ones). diff --git a/pkg/workflow/jsweep_workflow_test.go b/pkg/workflow/jsweep_workflow_test.go index d33486c77ba..9483a208af6 100644 --- a/pkg/workflow/jsweep_workflow_test.go +++ b/pkg/workflow/jsweep_workflow_test.go @@ -206,6 +206,13 @@ func TestJSweepWorkflowConfiguration(t *testing.T) { t.Error("jsweep workflow should batch validation commands into a single chained command") } }) + + // Test 17: Verify the branch prefix is set to "signed/" + t.Run("BranchPrefixSignedSlash", func(t *testing.T) { + if !strings.Contains(mdContent, `branch-prefix: "signed/"`) { + t.Error(`jsweep workflow should set branch-prefix to "signed/" in create-pull-request safe-outputs`) + } + }) } // TestJSweepWorkflowLockFile validates that the compiled jsweep.lock.yml file diff --git a/pkg/workflow/tool_description_enhancer.go b/pkg/workflow/tool_description_enhancer.go index 01902c44366..99cada0b799 100644 --- a/pkg/workflow/tool_description_enhancer.go +++ b/pkg/workflow/tool_description_enhancer.go @@ -201,6 +201,9 @@ func enhanceToolDescription(toolName, baseDescription string, safeOutputs *SafeO if templatableIntValue(config.Max) > 0 { constraints = append(constraints, fmt.Sprintf("Maximum %d pull request(s) can be created.", templatableIntValue(config.Max))) } + if config.BranchPrefix != "" { + constraints = append(constraints, fmt.Sprintf("Branch name will be prefixed with %q.", config.BranchPrefix)) + } if config.TitlePrefix != "" { constraints = append(constraints, fmt.Sprintf("Title will be prefixed with %q.", config.TitlePrefix)) } From 220babe4033d4ef0cbf0d3792ec98dcb01b1159f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 23:44:54 +0000 Subject: [PATCH 2/3] fix: validate and normalize branch-prefix against allowed git ref characters Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/create_pull_request.cjs | 11 ++++++++++- actions/setup/js/create_pull_request.test.cjs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index ed2a11fdba4..6bafd318f09 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -614,7 +614,16 @@ async function handleRemoteBranchCollision(branchName, preserveBranchName, optio */ async function main(config = {}) { // Extract configuration - const branchPrefix = config.branch_prefix || ""; + const rawBranchPrefix = config.branch_prefix || ""; + const normalizedBranchPrefix = rawBranchPrefix ? normalizeBranchName(rawBranchPrefix) : ""; + if (rawBranchPrefix && normalizedBranchPrefix !== rawBranchPrefix) { + core.warning( + `Branch prefix "${rawBranchPrefix}" contains characters that are invalid in a git ref. ` + + `Using normalized prefix: "${normalizedBranchPrefix}". ` + + `Update branch-prefix in the workflow configuration to avoid this warning.` + ); + } + const branchPrefix = normalizedBranchPrefix; const titlePrefix = config.title_prefix || ""; const envLabels = parseStringListConfig(config.labels); const configFallbackLabels = parseStringListConfig(config.fallback_labels); diff --git a/actions/setup/js/create_pull_request.test.cjs b/actions/setup/js/create_pull_request.test.cjs index a1ed94045da..062fb50073d 100644 --- a/actions/setup/js/create_pull_request.test.cjs +++ b/actions/setup/js/create_pull_request.test.cjs @@ -2694,4 +2694,16 @@ describe("create_pull_request - branch-prefix config", () => { expect(branchArg).toMatch(/^jsweep-/); expect(branchArg).not.toContain("signed/"); }); + + it("should normalize an invalid branch-prefix and emit a warning", async () => { + const { main } = require("./create_pull_request.cjs"); + const handler = await main({ branch_prefix: "bad prefix: ", allow_empty: true }); + + await handler({ title: "Test PR", body: "body" }, {}); + + expect(global.core.warning).toHaveBeenCalledWith(expect.stringMatching(/branch prefix.*characters that are invalid/i)); + const branchArg = global.github.rest.pulls.create.mock.calls[0][0].head; + // normalized prefix "bad-prefix" should be applied + expect(branchArg).toMatch(/^bad-prefix/); + }); }); From 7ae18da8cc975bf224dd2474d2c3844b45ed24e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 23:45:30 +0000 Subject: [PATCH 3/3] refactor: simplify branch-prefix normalization ternary Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/create_pull_request.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index 6bafd318f09..eb59cee00e7 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -615,7 +615,7 @@ async function handleRemoteBranchCollision(branchName, preserveBranchName, optio async function main(config = {}) { // Extract configuration const rawBranchPrefix = config.branch_prefix || ""; - const normalizedBranchPrefix = rawBranchPrefix ? normalizeBranchName(rawBranchPrefix) : ""; + const normalizedBranchPrefix = normalizeBranchName(rawBranchPrefix); if (rawBranchPrefix && normalizedBranchPrefix !== rawBranchPrefix) { core.warning( `Branch prefix "${rawBranchPrefix}" contains characters that are invalid in a git ref. ` +