From eecbde572586e7935b0db2a1523c3d53236a7330 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:01:15 +0000 Subject: [PATCH 1/9] Initial plan From 0a14aef27430143954ff08618d9bc0c68cadf885 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:11:24 +0000 Subject: [PATCH 2/9] Narrow submit_pull_request_review event enum by allowed-events config Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/safe_outputs_tools_generation.go | 17 ++++++++ .../safe_outputs_tools_generation_test.go | 39 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/pkg/workflow/safe_outputs_tools_generation.go b/pkg/workflow/safe_outputs_tools_generation.go index 83e22df5dfa..f55aaa40ab7 100644 --- a/pkg/workflow/safe_outputs_tools_generation.go +++ b/pkg/workflow/safe_outputs_tools_generation.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "path/filepath" + "strings" "github.com/github/gh-aw/pkg/sliceutil" "github.com/github/gh-aw/pkg/stringutil" @@ -341,6 +342,22 @@ func computePropertyInjections(safeOutputs *SafeOutputsConfig) map[string]map[st } } + // submit_pull_request_review event: when allowed-events restricts the set of review + // decisions, narrow the tool schema's event enum to match so the agent cannot select + // an event that runtime policy will reject. This retains runtime enforcement as + // defense in depth while preventing the doomed call in the first place. + if safeOutputs.SubmitPullRequestReview != nil && len(safeOutputs.SubmitPullRequestReview.AllowedEvents) > 0 { + allowedEvents := safeOutputs.SubmitPullRequestReview.AllowedEvents + injections["submit_pull_request_review"] = map[string]any{ + "event": map[string]any{ + "type": "string", + "enum": allowedEvents, + "description": "Review decision. Restricted by allowed-events configuration to: " + strings.Join(allowedEvents, ", ") + ".", + "x-synonyms": []string{"action"}, + }, + } + } + if safeOutputs.DataEnabled { dataProperty := map[string]any{"$ref": "#/0/inputSchema/$defs/structured_data"} if safeOutputs.NormalizedDataSchema != nil { diff --git a/pkg/workflow/safe_outputs_tools_generation_test.go b/pkg/workflow/safe_outputs_tools_generation_test.go index d5cf62a6a46..7958aa19be5 100644 --- a/pkg/workflow/safe_outputs_tools_generation_test.go +++ b/pkg/workflow/safe_outputs_tools_generation_test.go @@ -588,6 +588,45 @@ func TestComputePropertyInjectionsNilCloseIssues(t *testing.T) { assert.Empty(t, injections) } +// TestComputePropertyInjectionsAllowedEventsSubmitPRReview verifies that a configured +// allowed-events list narrows the submit_pull_request_review event enum in the tool schema. +func TestComputePropertyInjectionsAllowedEventsSubmitPRReview(t *testing.T) { + injections := computePropertyInjections(&SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + AllowedEvents: []string{"COMMENT"}, + }, + }) + + require.Contains(t, injections, "submit_pull_request_review") + prop, ok := injections["submit_pull_request_review"]["event"].(map[string]any) + require.True(t, ok, "event should be a property map") + assert.Equal(t, []string{"COMMENT"}, prop["enum"]) +} + +// TestComputePropertyInjectionsAllowedEventsMultipleSubmitPRReview verifies multiple allowed +// events are all present in the narrowed enum. +func TestComputePropertyInjectionsAllowedEventsMultipleSubmitPRReview(t *testing.T) { + injections := computePropertyInjections(&SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + AllowedEvents: []string{"COMMENT", "REQUEST_CHANGES"}, + }, + }) + + prop, ok := injections["submit_pull_request_review"]["event"].(map[string]any) + require.True(t, ok) + assert.Equal(t, []string{"COMMENT", "REQUEST_CHANGES"}, prop["enum"]) +} + +// TestComputePropertyInjectionsNoAllowedEventsSubmitPRReview verifies that no injection +// happens when allowed-events is not configured, so the static schema's full enum applies. +func TestComputePropertyInjectionsNoAllowedEventsSubmitPRReview(t *testing.T) { + injections := computePropertyInjections(&SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{}, + }) + + assert.NotContains(t, injections, "submit_pull_request_review", "no allowed-events should not inject an event enum") +} + // TestPreprocessStateReasonListSlice verifies that a []any slice is converted to allowed-state-reason. func TestPreprocessStateReasonListSlice(t *testing.T) { configData := map[string]any{ From ed2b968072cd6a4440c07884af7c1ff99c8ad798 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:12:19 +0000 Subject: [PATCH 3/9] Address code review: consistent assertion message in test Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/safe_outputs_tools_generation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/workflow/safe_outputs_tools_generation_test.go b/pkg/workflow/safe_outputs_tools_generation_test.go index 7958aa19be5..25722315372 100644 --- a/pkg/workflow/safe_outputs_tools_generation_test.go +++ b/pkg/workflow/safe_outputs_tools_generation_test.go @@ -613,7 +613,7 @@ func TestComputePropertyInjectionsAllowedEventsMultipleSubmitPRReview(t *testing }) prop, ok := injections["submit_pull_request_review"]["event"].(map[string]any) - require.True(t, ok) + require.True(t, ok, "event should be a property map") assert.Equal(t, []string{"COMMENT", "REQUEST_CHANGES"}, prop["enum"]) } From bd76983958e814dff73a33a89cc202206195c295 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:27:57 +0000 Subject: [PATCH 4/9] Require submit_pull_request_review event when COMMENT is disallowed Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/ponytail-reviewer.lock.yml | 16 ++++++++++++++- .../pr-code-quality-reviewer.lock.yml | 17 +++++++++++++++- pkg/workflow/safe_outputs_tools_generation.go | 6 ++++++ .../safe_outputs_tools_generation_test.go | 20 +++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ponytail-reviewer.lock.yml b/.github/workflows/ponytail-reviewer.lock.yml index 97ac35be3e9..ca09f7dc19c 100644 --- a/.github/workflows/ponytail-reviewer.lock.yml +++ b/.github/workflows/ponytail-reviewer.lock.yml @@ -691,7 +691,21 @@ jobs: "submit_pull_request_review": " CONSTRAINTS: Maximum 1 review(s) can be submitted." }, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [], + "property_injections": { + "submit_pull_request_review": { + "event": { + "description": "Review decision. Restricted by allowed-events configuration to: COMMENT.", + "enum": [ + "COMMENT" + ], + "type": "string", + "x-synonyms": [ + "action" + ] + } + } + } } GH_AW_VALIDATION_JSON: | { diff --git a/.github/workflows/pr-code-quality-reviewer.lock.yml b/.github/workflows/pr-code-quality-reviewer.lock.yml index 6003e989214..73c5cd45012 100644 --- a/.github/workflows/pr-code-quality-reviewer.lock.yml +++ b/.github/workflows/pr-code-quality-reviewer.lock.yml @@ -665,7 +665,22 @@ jobs: "submit_pull_request_review": " CONSTRAINTS: Maximum 1 review(s) can be submitted." }, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [], + "property_injections": { + "submit_pull_request_review": { + "event": { + "description": "Review decision. Restricted by allowed-events configuration to: COMMENT, REQUEST_CHANGES.", + "enum": [ + "COMMENT", + "REQUEST_CHANGES" + ], + "type": "string", + "x-synonyms": [ + "action" + ] + } + } + } } GH_AW_VALIDATION_JSON: | { diff --git a/pkg/workflow/safe_outputs_tools_generation.go b/pkg/workflow/safe_outputs_tools_generation.go index f55aaa40ab7..81ea0507713 100644 --- a/pkg/workflow/safe_outputs_tools_generation.go +++ b/pkg/workflow/safe_outputs_tools_generation.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "path/filepath" + "slices" "strings" "github.com/github/gh-aw/pkg/sliceutil" @@ -278,6 +279,11 @@ func computeRequiredFieldAdditions(safeOutputs *SafeOutputsConfig) map[string][] if safeOutputs.AssignToAgent != nil && issueIntentRequired(safeOutputs.AssignToAgent.IssueIntent) { additions["assign_to_agent"] = issueIntentRequiredFields } + if safeOutputs.SubmitPullRequestReview != nil && len(safeOutputs.SubmitPullRequestReview.AllowedEvents) > 0 { + if !slices.Contains(safeOutputs.SubmitPullRequestReview.AllowedEvents, "COMMENT") { + additions["submit_pull_request_review"] = []string{"event"} + } + } return additions } diff --git a/pkg/workflow/safe_outputs_tools_generation_test.go b/pkg/workflow/safe_outputs_tools_generation_test.go index 25722315372..3a34a73cc3a 100644 --- a/pkg/workflow/safe_outputs_tools_generation_test.go +++ b/pkg/workflow/safe_outputs_tools_generation_test.go @@ -509,6 +509,26 @@ func TestComputeRequiredFieldAdditionsDisabledByDefault(t *testing.T) { assert.Empty(t, additions) } +func TestComputeRequiredFieldAdditionsSubmitPRReviewEventRequiredWhenCommentDisallowed(t *testing.T) { + additions := computeRequiredFieldAdditions(&SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + AllowedEvents: []string{"APPROVE"}, + }, + }) + + assert.Equal(t, []string{"event"}, additions["submit_pull_request_review"]) +} + +func TestComputeRequiredFieldAdditionsSubmitPRReviewEventOptionalWhenCommentAllowed(t *testing.T) { + additions := computeRequiredFieldAdditions(&SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + AllowedEvents: []string{"COMMENT", "REQUEST_CHANGES"}, + }, + }) + + assert.NotContains(t, additions, "submit_pull_request_review") +} + func TestComputeRequiredFieldAdditionsIssueIntentDefaultDisabled(t *testing.T) { additions := computeRequiredFieldAdditions(&SafeOutputsConfig{ CloseIssues: &CloseIssuesConfig{}, From 02900e6bb0afea19d76aba8859bff97a6df62569 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:51:00 +0000 Subject: [PATCH 5/9] Improve tools meta marshal errors for errormessage lint Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/safe_outputs_tools_generation.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/safe_outputs_tools_generation.go b/pkg/workflow/safe_outputs_tools_generation.go index 81ea0507713..811e90d1a32 100644 --- a/pkg/workflow/safe_outputs_tools_generation.go +++ b/pkg/workflow/safe_outputs_tools_generation.go @@ -397,7 +397,7 @@ func generateToolsMetaJSON(data *WorkflowData, markdownPath string) (string, err } result, err := json.Marshal(empty) if err != nil { - return "", fmt.Errorf("failed to marshal empty tools meta: %w", err) + return "", fmt.Errorf("marshal empty tools metadata to JSON for default safe-outputs behavior: %w", err) } return string(result), nil } @@ -454,7 +454,7 @@ func generateToolsMetaJSON(data *WorkflowData, markdownPath string) (string, err result, err := json.MarshalIndent(meta, "", " ") if err != nil { safeOutputsConfigLog.Printf("Failed to marshal tools meta: %v", err) - return "", fmt.Errorf("failed to marshal tools meta: %w", err) + return "", fmt.Errorf("marshal tools metadata to JSON for runtime schema injection; verify safe-outputs values are JSON-serializable: %w", err) } safeOutputsConfigLog.Printf("Successfully generated tools meta JSON: %d description suffixes, %d repo params, %d dynamic tools", From 1ffd58e1b8a9d155608b5b8e486be83e5936bcff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:54:08 +0000 Subject: [PATCH 6/9] Refine tools meta error wording for CI linter Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/safe_outputs_tools_generation.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/safe_outputs_tools_generation.go b/pkg/workflow/safe_outputs_tools_generation.go index 811e90d1a32..c7ff4280194 100644 --- a/pkg/workflow/safe_outputs_tools_generation.go +++ b/pkg/workflow/safe_outputs_tools_generation.go @@ -397,7 +397,7 @@ func generateToolsMetaJSON(data *WorkflowData, markdownPath string) (string, err } result, err := json.Marshal(empty) if err != nil { - return "", fmt.Errorf("marshal empty tools metadata to JSON for default safe-outputs behavior: %w", err) + return "", fmt.Errorf("unable to marshal empty tools metadata to JSON for default safe-outputs behavior: %w", err) } return string(result), nil } @@ -454,7 +454,7 @@ func generateToolsMetaJSON(data *WorkflowData, markdownPath string) (string, err result, err := json.MarshalIndent(meta, "", " ") if err != nil { safeOutputsConfigLog.Printf("Failed to marshal tools meta: %v", err) - return "", fmt.Errorf("marshal tools metadata to JSON for runtime schema injection; verify safe-outputs values are JSON-serializable: %w", err) + return "", fmt.Errorf("unable to marshal tools metadata to JSON for runtime schema injection; verify safe-outputs values are JSON-serializable: %w", err) } safeOutputsConfigLog.Printf("Successfully generated tools meta JSON: %d description suffixes, %d repo params, %d dynamic tools", From 87b6f39ae9d2238f989de2e504867dc0e28a34eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:46:32 +0000 Subject: [PATCH 7/9] chore: triage review feedback Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.sum b/go.sum index 207963a0f66..73c56b14254 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60= -charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo= charm.land/bubbles/v2 v2.2.0 h1:9GEMewcejrNtVIxZ9Y2wSsWJamsWNsXa8MpMLnH4yI4= charm.land/bubbles/v2 v2.2.0/go.mod h1:wdMgn+sje1KNXdwFizIWjbf328fIUBxqEmJ/vYPo8yc= charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY= @@ -144,8 +142,6 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= -github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= From cf9c36e6194f488dc45346d32d1426e8766517be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:53:43 +0000 Subject: [PATCH 8/9] Add nil submit PR review property-injection test Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/workflow/safe_outputs_tools_generation_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/workflow/safe_outputs_tools_generation_test.go b/pkg/workflow/safe_outputs_tools_generation_test.go index 3a34a73cc3a..987c295645a 100644 --- a/pkg/workflow/safe_outputs_tools_generation_test.go +++ b/pkg/workflow/safe_outputs_tools_generation_test.go @@ -608,6 +608,15 @@ func TestComputePropertyInjectionsNilCloseIssues(t *testing.T) { assert.Empty(t, injections) } +// TestComputePropertyInjectionsNilSubmitPRReview verifies that nil submit-pull-request-review +// does not add submit_pull_request_review property injections. +func TestComputePropertyInjectionsNilSubmitPRReview(t *testing.T) { + injections := computePropertyInjections(&SafeOutputsConfig{ + SubmitPullRequestReview: nil, + }) + assert.NotContains(t, injections, "submit_pull_request_review") +} + // TestComputePropertyInjectionsAllowedEventsSubmitPRReview verifies that a configured // allowed-events list narrows the submit_pull_request_review event enum in the tool schema. func TestComputePropertyInjectionsAllowedEventsSubmitPRReview(t *testing.T) { From 306333cfe393ba24fc70e9bee84d9e7bc70e3f5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:13:45 +0000 Subject: [PATCH 9/9] Refresh branch and fix stale wasm golden for MCP Server casing Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../TestWasmGolden_CompileFixtures/smoke-copilot.golden | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden index 77630551569..74c7e1a1a4c 100644 --- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden +++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden @@ -511,10 +511,10 @@ jobs: gh aw version env: GH_TOKEN: ${{ github.token }} - - name: Copy gh-aw binary for MCP server + - name: Copy gh-aw binary for MCP Server run: | gh aw --version - # Copy the gh-aw binary to ${RUNNER_TEMP}/gh-aw for MCP server containerization + # Copy the gh-aw binary to ${RUNNER_TEMP}/gh-aw for MCP Server containerization mkdir -p "${RUNNER_TEMP}/gh-aw" GH_AW_BIN="" GH_AW_BIN=$(command -v gh-aw 2>/dev/null) || true @@ -532,7 +532,7 @@ jobs: chmod +x "${RUNNER_TEMP}/gh-aw/gh-aw" echo "Copied gh-aw binary to ${RUNNER_TEMP}/gh-aw/gh-aw" else - echo "::error::Failed to find gh-aw binary for MCP server" + echo "::error::Failed to find gh-aw binary for MCP Server" exit 1 fi - name: Start MCP Gateway