From d4633e64251b676db818b886454ae377661223e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:42:28 +0000 Subject: [PATCH 1/5] Initial plan From dac26659eecfef2c49a027315e1f30652cbf2de4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:52:56 +0000 Subject: [PATCH 2/5] Initial plan for fixing failing integration tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-copilot-token-report.lock.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/daily-copilot-token-report.lock.yml b/.github/workflows/daily-copilot-token-report.lock.yml index 3d51283333..598c7d74ba 100644 --- a/.github/workflows/daily-copilot-token-report.lock.yml +++ b/.github/workflows/daily-copilot-token-report.lock.yml @@ -30,7 +30,6 @@ name: "Daily Copilot Token Consumption Report" "on": schedule: - cron: "0 11 * * 1-5" - # Friendly format: daily (scattered) workflow_dispatch: permissions: {} From 9607d8ea58aaccf29be399530b5a6ef67b23dcb6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:05:25 +0000 Subject: [PATCH 3/5] Add github-token property to safe output schemas and analyze token precedence issue Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 114 ++++++++++++++++++- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index ebb6ab3328..39e75b56ba 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -119,7 +119,23 @@ }, "on": { "description": "Workflow triggers that define when the agentic workflow should run. Supports standard GitHub Actions trigger events plus special command triggers for /commands (required)", - "examples": [{ "issues": { "types": ["opened"] } }, { "pull_request": { "types": ["opened", "synchronize"] } }, "workflow_dispatch", { "schedule": "daily at 9am" }, "/my-bot"], + "examples": [ + { + "issues": { + "types": ["opened"] + } + }, + { + "pull_request": { + "types": ["opened", "synchronize"] + } + }, + "workflow_dispatch", + { + "schedule": "daily at 9am" + }, + "/my-bot" + ], "oneOf": [ { "type": "string", @@ -2335,8 +2351,19 @@ ] }, "examples": [ - [{ "prompt": "Analyze the issue and create a plan" }], - [{ "uses": "actions/checkout@v4" }, { "prompt": "Review the code and suggest improvements" }], + [ + { + "prompt": "Analyze the issue and create a plan" + } + ], + [ + { + "uses": "actions/checkout@v4" + }, + { + "prompt": "Review the code and suggest improvements" + } + ], [ { "name": "Download logs from last 24 hours", @@ -2395,7 +2422,20 @@ "engine": { "description": "AI engine configuration that specifies which AI processor interprets and executes the markdown content of the workflow. Defaults to 'copilot'.", "default": "copilot", - "examples": ["copilot", "claude", "codex", { "id": "copilot", "version": "beta" }, { "id": "claude", "model": "claude-3-5-sonnet-20241022", "max-turns": 15 }], + "examples": [ + "copilot", + "claude", + "codex", + { + "id": "copilot", + "version": "beta" + }, + { + "id": "claude", + "model": "claude-3-5-sonnet-20241022", + "max-turns": 15 + } + ], "$ref": "#/$defs/engine_config" }, "mcp-servers": { @@ -2433,7 +2473,27 @@ "tools": { "type": "object", "description": "Tools and MCP (Model Context Protocol) servers available to the AI engine for GitHub API access, browser automation, file editing, and more", - "examples": [{ "playwright": { "version": "v1.41.0" } }, { "github": { "mode": "remote" } }, { "github": { "mode": "local", "version": "latest" } }, { "bash": null }], + "examples": [ + { + "playwright": { + "version": "v1.41.0" + } + }, + { + "github": { + "mode": "remote" + } + }, + { + "github": { + "mode": "local", + "version": "latest" + } + }, + { + "bash": null + } + ], "properties": { "github": { "description": "GitHub API tools for repository operations (issues, pull requests, content management)", @@ -3446,6 +3506,10 @@ } ], "description": "Time until the issue expires and should be automatically closed. Supports integer (days) or relative time format. When set, a maintenance workflow will be generated." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3604,6 +3668,10 @@ } ], "description": "Time until the discussion expires and should be automatically closed. Supports integer (days) or relative time format like '2h' (2 hours), '7d' (7 days), '2w' (2 weeks), '1m' (1 month), '1y' (1 year). When set, a maintenance workflow will be generated." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3675,6 +3743,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3732,6 +3804,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository discussion updates. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -3772,6 +3848,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3880,6 +3960,10 @@ "type": "string", "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] } + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -4334,6 +4418,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository issue updates. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4459,6 +4547,10 @@ "type": "string", "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] } + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4586,6 +4678,10 @@ "type": "string", "description": "Target repository for cross-repo release updates (format: owner/repo). If not specified, updates releases in the workflow's repository.", "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$" + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4693,6 +4789,10 @@ "items": { "$ref": "#/properties/githubActionsStep" } + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4903,6 +5003,10 @@ "description": "Maximum number of mentions allowed per message. Default: 50", "minimum": 1, "default": 50 + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false From 3ec50f368aa17e1e2bc03d93b82dd091e3d64379 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:12:44 +0000 Subject: [PATCH 4/5] Fix individual github-token support for handler-managed safe outputs - Add getFirstHandlerIndividualToken() to get individual token from handlers - Update handler manager step to use individual token if available - Update tests to expect process_safe_outputs step for handler-managed types - All github token precedence tests now pass Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-choice-test.lock.yml | 4 +- pkg/workflow/compiler_safe_outputs_core.go | 49 ++++++++++++++++++- ...ndividual_github_token_integration_test.go | 7 +-- pkg/workflow/safe_outputs_integration_test.go | 3 +- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/.github/workflows/daily-choice-test.lock.yml b/.github/workflows/daily-choice-test.lock.yml index 1cbb37ed40..d3ec2d747e 100644 --- a/.github/workflows/daily-choice-test.lock.yml +++ b/.github/workflows/daily-choice-test.lock.yml @@ -278,8 +278,8 @@ jobs: } }, "required": [ - "environment", - "test_type" + "test_type", + "environment" ], "type": "object" }, diff --git a/pkg/workflow/compiler_safe_outputs_core.go b/pkg/workflow/compiler_safe_outputs_core.go index e1b59902b7..63560ea43a 100644 --- a/pkg/workflow/compiler_safe_outputs_core.go +++ b/pkg/workflow/compiler_safe_outputs_core.go @@ -646,8 +646,11 @@ func (c *Compiler) buildHandlerManagerStep(data *WorkflowData) []string { c.addAllSafeOutputConfigEnvVars(&steps, data) // With section for github-token + // Use the first individual handler token if any handler has one, + // otherwise fall back to global safe-outputs token + individualToken := c.getFirstHandlerIndividualToken(data) steps = append(steps, " with:\n") - c.addSafeOutputGitHubTokenForConfig(&steps, data, "") + c.addSafeOutputGitHubTokenForConfig(&steps, data, individualToken) steps = append(steps, " script: |\n") steps = append(steps, " const { setupGlobals } = require('"+SetupActionDestination+"/setup_globals.cjs');\n") @@ -658,6 +661,50 @@ func (c *Compiler) buildHandlerManagerStep(data *WorkflowData) []string { return steps } +// getFirstHandlerIndividualToken returns the first non-empty individual token from handler-managed safe outputs +// This is used when multiple handlers are consolidated into a single step and we need to determine +// which token to use. Returns empty string if no individual tokens are set. +func (c *Compiler) getFirstHandlerIndividualToken(data *WorkflowData) string { + if data.SafeOutputs == nil { + return "" + } + + // Check each handler-managed safe output type for an individual token + // Order matches the order they are checked in buildConsolidatedSafeOutputsJob + if data.SafeOutputs.CreateIssues != nil && data.SafeOutputs.CreateIssues.GitHubToken != "" { + return data.SafeOutputs.CreateIssues.GitHubToken + } + if data.SafeOutputs.AddComments != nil && data.SafeOutputs.AddComments.GitHubToken != "" { + return data.SafeOutputs.AddComments.GitHubToken + } + if data.SafeOutputs.CreateDiscussions != nil && data.SafeOutputs.CreateDiscussions.GitHubToken != "" { + return data.SafeOutputs.CreateDiscussions.GitHubToken + } + if data.SafeOutputs.CloseIssues != nil && data.SafeOutputs.CloseIssues.GitHubToken != "" { + return data.SafeOutputs.CloseIssues.GitHubToken + } + if data.SafeOutputs.CloseDiscussions != nil && data.SafeOutputs.CloseDiscussions.GitHubToken != "" { + return data.SafeOutputs.CloseDiscussions.GitHubToken + } + if data.SafeOutputs.AddLabels != nil && data.SafeOutputs.AddLabels.GitHubToken != "" { + return data.SafeOutputs.AddLabels.GitHubToken + } + if data.SafeOutputs.UpdateIssues != nil && data.SafeOutputs.UpdateIssues.GitHubToken != "" { + return data.SafeOutputs.UpdateIssues.GitHubToken + } + if data.SafeOutputs.UpdateDiscussions != nil && data.SafeOutputs.UpdateDiscussions.GitHubToken != "" { + return data.SafeOutputs.UpdateDiscussions.GitHubToken + } + if data.SafeOutputs.LinkSubIssue != nil && data.SafeOutputs.LinkSubIssue.GitHubToken != "" { + return data.SafeOutputs.LinkSubIssue.GitHubToken + } + if data.SafeOutputs.UpdateRelease != nil && data.SafeOutputs.UpdateRelease.GitHubToken != "" { + return data.SafeOutputs.UpdateRelease.GitHubToken + } + + return "" +} + // addHandlerManagerConfigEnvVar adds a JSON config environment variable for the handler manager // This config indicates which handlers should be loaded and includes their type-specific options // The presence of a config key indicates that handler is enabled (no explicit "enabled" field needed) diff --git a/pkg/workflow/individual_github_token_integration_test.go b/pkg/workflow/individual_github_token_integration_test.go index f76986d9e6..1bdd234ed4 100644 --- a/pkg/workflow/individual_github_token_integration_test.go +++ b/pkg/workflow/individual_github_token_integration_test.go @@ -191,9 +191,10 @@ This workflow tests that add-labels uses its own github-token. yamlContent := string(content) - // Verify that the safe_outputs job is generated with add_labels step - if !strings.Contains(yamlContent, "id: add_labels") { - t.Error("Expected safe_outputs job with add_labels step to be generated") + // Verify that the safe_outputs job is generated with handler manager step + // (add-labels is now handled by the consolidated handler manager) + if !strings.Contains(yamlContent, "id: process_safe_outputs") { + t.Error("Expected safe_outputs job with process_safe_outputs step to be generated") } if !strings.Contains(yamlContent, "github-token: ${{ secrets.LABELS_PAT }}") { diff --git a/pkg/workflow/safe_outputs_integration_test.go b/pkg/workflow/safe_outputs_integration_test.go index 0dd313a188..77d2ca3eb9 100644 --- a/pkg/workflow/safe_outputs_integration_test.go +++ b/pkg/workflow/safe_outputs_integration_test.go @@ -517,9 +517,8 @@ func TestConsolidatedSafeOutputsJobIntegration(t *testing.T) { "SHARED_VAR", }, expectedStepNames: []string{ - "create_issue", + "process_safe_outputs", // create_issue and add_comment are handled by the handler manager "create_pull_request", - "add_comment", // Note: "noop" is not included in consolidated job }, }, From 35d18f294a866c7b32ba5dfd6a157cfdc5e4d733 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:16:55 +0000 Subject: [PATCH 5/5] Complete test fixes - all tests passing - Formatted code with go fmt - Recompiled all workflows successfully - All unit and integration tests pass - Ready for merge Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/aw/schemas/agentic-workflow.json | 114 ++++++++++++++++++- .github/workflows/daily-choice-test.lock.yml | 4 +- 2 files changed, 111 insertions(+), 7 deletions(-) diff --git a/.github/aw/schemas/agentic-workflow.json b/.github/aw/schemas/agentic-workflow.json index ebb6ab3328..39e75b56ba 100644 --- a/.github/aw/schemas/agentic-workflow.json +++ b/.github/aw/schemas/agentic-workflow.json @@ -119,7 +119,23 @@ }, "on": { "description": "Workflow triggers that define when the agentic workflow should run. Supports standard GitHub Actions trigger events plus special command triggers for /commands (required)", - "examples": [{ "issues": { "types": ["opened"] } }, { "pull_request": { "types": ["opened", "synchronize"] } }, "workflow_dispatch", { "schedule": "daily at 9am" }, "/my-bot"], + "examples": [ + { + "issues": { + "types": ["opened"] + } + }, + { + "pull_request": { + "types": ["opened", "synchronize"] + } + }, + "workflow_dispatch", + { + "schedule": "daily at 9am" + }, + "/my-bot" + ], "oneOf": [ { "type": "string", @@ -2335,8 +2351,19 @@ ] }, "examples": [ - [{ "prompt": "Analyze the issue and create a plan" }], - [{ "uses": "actions/checkout@v4" }, { "prompt": "Review the code and suggest improvements" }], + [ + { + "prompt": "Analyze the issue and create a plan" + } + ], + [ + { + "uses": "actions/checkout@v4" + }, + { + "prompt": "Review the code and suggest improvements" + } + ], [ { "name": "Download logs from last 24 hours", @@ -2395,7 +2422,20 @@ "engine": { "description": "AI engine configuration that specifies which AI processor interprets and executes the markdown content of the workflow. Defaults to 'copilot'.", "default": "copilot", - "examples": ["copilot", "claude", "codex", { "id": "copilot", "version": "beta" }, { "id": "claude", "model": "claude-3-5-sonnet-20241022", "max-turns": 15 }], + "examples": [ + "copilot", + "claude", + "codex", + { + "id": "copilot", + "version": "beta" + }, + { + "id": "claude", + "model": "claude-3-5-sonnet-20241022", + "max-turns": 15 + } + ], "$ref": "#/$defs/engine_config" }, "mcp-servers": { @@ -2433,7 +2473,27 @@ "tools": { "type": "object", "description": "Tools and MCP (Model Context Protocol) servers available to the AI engine for GitHub API access, browser automation, file editing, and more", - "examples": [{ "playwright": { "version": "v1.41.0" } }, { "github": { "mode": "remote" } }, { "github": { "mode": "local", "version": "latest" } }, { "bash": null }], + "examples": [ + { + "playwright": { + "version": "v1.41.0" + } + }, + { + "github": { + "mode": "remote" + } + }, + { + "github": { + "mode": "local", + "version": "latest" + } + }, + { + "bash": null + } + ], "properties": { "github": { "description": "GitHub API tools for repository operations (issues, pull requests, content management)", @@ -3446,6 +3506,10 @@ } ], "description": "Time until the issue expires and should be automatically closed. Supports integer (days) or relative time format. When set, a maintenance workflow will be generated." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3604,6 +3668,10 @@ } ], "description": "Time until the discussion expires and should be automatically closed. Supports integer (days) or relative time format like '2h' (2 hours), '7d' (7 days), '2w' (2 weeks), '1m' (1 month), '1y' (1 year). When set, a maintenance workflow will be generated." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3675,6 +3743,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3732,6 +3804,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository discussion updates. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -3772,6 +3848,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -3880,6 +3960,10 @@ "type": "string", "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] } + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false, @@ -4334,6 +4418,10 @@ "target-repo": { "type": "string", "description": "Target repository in format 'owner/repo' for cross-repository issue updates. Takes precedence over trial target repo settings." + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4459,6 +4547,10 @@ "type": "string", "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] } + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4586,6 +4678,10 @@ "type": "string", "description": "Target repository for cross-repo release updates (format: owner/repo). If not specified, updates releases in the workflow's repository.", "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$" + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4693,6 +4789,10 @@ "items": { "$ref": "#/properties/githubActionsStep" } + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false @@ -4903,6 +5003,10 @@ "description": "Maximum number of mentions allowed per message. Default: 50", "minimum": 1, "default": 50 + }, + "github-token": { + "type": "string", + "description": "GitHub token to use for this safe output operation. Overrides the global safe-outputs github-token if specified. Supports GitHub Actions expressions like ${{ secrets.MY_TOKEN }}." } }, "additionalProperties": false diff --git a/.github/workflows/daily-choice-test.lock.yml b/.github/workflows/daily-choice-test.lock.yml index d3ec2d747e..1cbb37ed40 100644 --- a/.github/workflows/daily-choice-test.lock.yml +++ b/.github/workflows/daily-choice-test.lock.yml @@ -278,8 +278,8 @@ jobs: } }, "required": [ - "test_type", - "environment" + "environment", + "test_type" ], "type": "object" },