From 147ed222baf58c2a2304d3adf5324ea47ec748bd Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 24 Feb 2026 00:02:53 +0000 Subject: [PATCH 1/6] CI trigger token can be magic secret --- actions/setup/js/extra_empty_commit.cjs | 4 +-- actions/setup/js/extra_empty_commit.test.cjs | 18 ++++++------- pkg/parser/schemas/main_workflow_schema.json | 4 +-- pkg/workflow/compiler_safe_outputs_job.go | 20 +++++++-------- pkg/workflow/create_pull_request.go | 27 ++++++++++---------- 5 files changed, 36 insertions(+), 37 deletions(-) diff --git a/actions/setup/js/extra_empty_commit.cjs b/actions/setup/js/extra_empty_commit.cjs index 185af5fad4..130ad4f25d 100644 --- a/actions/setup/js/extra_empty_commit.cjs +++ b/actions/setup/js/extra_empty_commit.cjs @@ -9,7 +9,7 @@ * GITHUB_TOKEN do not trigger other workflow runs. * * The token comes from `github-token-for-extra-empty-commit` in safe-outputs config - * (passed as GH_AW_EXTRA_EMPTY_COMMIT_TOKEN env var). Supported values: + * (passed as GH_AW_CI_TRIGGER_TOKEN env var). Supported values: * - `app` - Use GitHub App token from safe-outputs-app-token step * - `default` - Use the magic secret GH_AW_CI_TRIGGER_TOKEN * - `${{ secrets.CUSTOM_TOKEN }}` - Use a custom PAT or secret @@ -28,7 +28,7 @@ * @returns {Promise<{success: boolean, skipped?: boolean, error?: string}>} */ async function pushExtraEmptyCommit({ branchName, repoOwner, repoName, commitMessage }) { - const token = process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN; + const token = process.env.GH_AW_CI_TRIGGER_TOKEN; if (!token || !token.trim()) { core.info("No extra empty commit token configured - skipping"); diff --git a/actions/setup/js/extra_empty_commit.test.cjs b/actions/setup/js/extra_empty_commit.test.cjs index 3ba863a64b..d3b69e7488 100644 --- a/actions/setup/js/extra_empty_commit.test.cjs +++ b/actions/setup/js/extra_empty_commit.test.cjs @@ -7,7 +7,7 @@ describe("extra_empty_commit.cjs", () => { let originalEnv; beforeEach(() => { - originalEnv = process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN; + originalEnv = process.env.GH_AW_CI_TRIGGER_TOKEN; mockCore = { info: vi.fn(), @@ -30,9 +30,9 @@ describe("extra_empty_commit.cjs", () => { afterEach(() => { if (originalEnv !== undefined) { - process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN = originalEnv; + process.env.GH_AW_CI_TRIGGER_TOKEN = originalEnv; } else { - delete process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN; + delete process.env.GH_AW_CI_TRIGGER_TOKEN; } delete global.core; delete global.exec; @@ -59,7 +59,7 @@ describe("extra_empty_commit.cjs", () => { describe("when no extra empty commit token is set", () => { it("should skip and return success with skipped=true", async () => { - delete process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN; + delete process.env.GH_AW_CI_TRIGGER_TOKEN; ({ pushExtraEmptyCommit } = require("./extra_empty_commit.cjs")); const result = await pushExtraEmptyCommit({ @@ -74,7 +74,7 @@ describe("extra_empty_commit.cjs", () => { }); it("should skip when token is empty string", async () => { - process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN = ""; + process.env.GH_AW_CI_TRIGGER_TOKEN = ""; ({ pushExtraEmptyCommit } = require("./extra_empty_commit.cjs")); const result = await pushExtraEmptyCommit({ @@ -87,7 +87,7 @@ describe("extra_empty_commit.cjs", () => { }); it("should skip when token is whitespace only", async () => { - process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN = " "; + process.env.GH_AW_CI_TRIGGER_TOKEN = " "; ({ pushExtraEmptyCommit } = require("./extra_empty_commit.cjs")); const result = await pushExtraEmptyCommit({ @@ -106,7 +106,7 @@ describe("extra_empty_commit.cjs", () => { describe("when token is set and no cycle issues", () => { beforeEach(() => { - process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN = "ghp_test_token_123"; + process.env.GH_AW_CI_TRIGGER_TOKEN = "ghp_test_token_123"; // Simulate git log showing 5 commits, all with file changes (non-empty) const logOutput = ["COMMIT:aaa111", "file1.txt", "", "COMMIT:bbb222", "file2.txt", "file3.txt", "", "COMMIT:ccc333", "file4.txt", "", "COMMIT:ddd444", "file5.txt", "", "COMMIT:eee555", "file6.txt", ""].join("\n"); mockGitLogOutput(logOutput); @@ -187,7 +187,7 @@ describe("extra_empty_commit.cjs", () => { describe("cycle prevention", () => { beforeEach(() => { - process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN = "ghp_test_token_123"; + process.env.GH_AW_CI_TRIGGER_TOKEN = "ghp_test_token_123"; ({ pushExtraEmptyCommit } = require("./extra_empty_commit.cjs")); }); @@ -327,7 +327,7 @@ describe("extra_empty_commit.cjs", () => { describe("error handling", () => { beforeEach(() => { - process.env.GH_AW_EXTRA_EMPTY_COMMIT_TOKEN = "ghp_test_token_123"; + process.env.GH_AW_CI_TRIGGER_TOKEN = "ghp_test_token_123"; // No empty commits in log mockGitLogOutput("COMMIT:abc123\nfile.txt\n"); ({ pushExtraEmptyCommit } = require("./extra_empty_commit.cjs")); diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index f7449cf915..b245c46dfe 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -5169,7 +5169,7 @@ }, "github-token-for-extra-empty-commit": { "type": "string", - "description": "Token used to push an empty commit after PR creation to trigger CI events. Works around the GITHUB_TOKEN limitation where pushes don't trigger workflow runs. Use a secret expression (e.g. '${{ secrets.CI_TOKEN }}'), 'app' for GitHub App auth, or 'default' to use the magic secret GH_AW_CI_TRIGGER_TOKEN." + "description": "Token used to push an empty commit after PR creation to trigger CI events. Works around the GITHUB_TOKEN limitation where pushes don't trigger workflow runs. Defaults to the magic secret GH_AW_CI_TRIGGER_TOKEN if set in the repository. Use a secret expression (e.g. '${{ secrets.CI_TOKEN }}') for a custom token, or 'app' for GitHub App auth." } }, "additionalProperties": false, @@ -6150,7 +6150,7 @@ }, "github-token-for-extra-empty-commit": { "type": "string", - "description": "Token used to push an empty commit after pushing changes to trigger CI events. Works around the GITHUB_TOKEN limitation where pushes don't trigger workflow runs. Use a secret expression (e.g. '${{ secrets.CI_TOKEN }}'), 'app' for GitHub App auth, or 'default' to use the magic secret GH_AW_CI_TRIGGER_TOKEN." + "description": "Token used to push an empty commit after pushing changes to trigger CI events. Works around the GITHUB_TOKEN limitation where pushes don't trigger workflow runs. Defaults to the magic secret GH_AW_CI_TRIGGER_TOKEN if set in the repository. Use a secret expression (e.g. '${{ secrets.CI_TOKEN }}') for a custom token, or 'app' for GitHub App auth." } }, "additionalProperties": false diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go index 0c3dc99050..78933948b4 100644 --- a/pkg/workflow/compiler_safe_outputs_job.go +++ b/pkg/workflow/compiler_safe_outputs_job.go @@ -409,22 +409,22 @@ func (c *Compiler) buildJobLevelSafeOutputEnvVars(data *WorkflowData, workflowID // This token is used to push an empty commit after code changes to trigger CI events, // working around the GITHUB_TOKEN limitation where events don't trigger other workflows. if data.SafeOutputs != nil { - var extraEmptyCommitToken string + var ciTriggerToken string if data.SafeOutputs.CreatePullRequests != nil && data.SafeOutputs.CreatePullRequests.GithubTokenForExtraEmptyCommit != "" { - extraEmptyCommitToken = data.SafeOutputs.CreatePullRequests.GithubTokenForExtraEmptyCommit + ciTriggerToken = data.SafeOutputs.CreatePullRequests.GithubTokenForExtraEmptyCommit } else if data.SafeOutputs.PushToPullRequestBranch != nil && data.SafeOutputs.PushToPullRequestBranch.GithubTokenForExtraEmptyCommit != "" { - extraEmptyCommitToken = data.SafeOutputs.PushToPullRequestBranch.GithubTokenForExtraEmptyCommit + ciTriggerToken = data.SafeOutputs.PushToPullRequestBranch.GithubTokenForExtraEmptyCommit } - if extraEmptyCommitToken == "app" { - envVars["GH_AW_EXTRA_EMPTY_COMMIT_TOKEN"] = "${{ steps.safe-outputs-app-token.outputs.token || '' }}" + if ciTriggerToken == "app" { + envVars["GH_AW_CI_TRIGGER_TOKEN"] = "${{ steps.safe-outputs-app-token.outputs.token || '' }}" consolidatedSafeOutputsJobLog.Print("Extra empty commit using GitHub App token") - } else if extraEmptyCommitToken == "default" { - // Use the magic GH_AW_CI_TRIGGER_TOKEN secret as fallback - envVars["GH_AW_EXTRA_EMPTY_COMMIT_TOKEN"] = getEffectiveCITriggerGitHubToken("") + } else if ciTriggerToken == "default" || ciTriggerToken == "" { + // Use the magic GH_AW_CI_TRIGGER_TOKEN secret (default behavior when not explicitly configured) + envVars["GH_AW_CI_TRIGGER_TOKEN"] = getEffectiveCITriggerGitHubToken("") consolidatedSafeOutputsJobLog.Print("Extra empty commit using GH_AW_CI_TRIGGER_TOKEN") - } else if extraEmptyCommitToken != "" { - envVars["GH_AW_EXTRA_EMPTY_COMMIT_TOKEN"] = extraEmptyCommitToken + } else { + envVars["GH_AW_CI_TRIGGER_TOKEN"] = ciTriggerToken consolidatedSafeOutputsJobLog.Print("Extra empty commit using explicit token") } } diff --git a/pkg/workflow/create_pull_request.go b/pkg/workflow/create_pull_request.go index 5f8a31c01d..5afb727d84 100644 --- a/pkg/workflow/create_pull_request.go +++ b/pkg/workflow/create_pull_request.go @@ -165,20 +165,19 @@ func (c *Compiler) buildCreateOutputPullRequestJob(data *WorkflowData, mainJobNa createPRLog.Print("Footer disabled - XML markers will be included but visible footer content will be omitted") } - // Add extra empty commit token if configured (for pushing an empty commit to trigger CI) - extraEmptyCommitToken := data.SafeOutputs.CreatePullRequests.GithubTokenForExtraEmptyCommit - if extraEmptyCommitToken != "" { - if extraEmptyCommitToken == "app" { - customEnvVars = append(customEnvVars, " GH_AW_EXTRA_EMPTY_COMMIT_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token || '' }}\n") - createPRLog.Print("Extra empty commit using GitHub App token") - } else if extraEmptyCommitToken == "default" { - // Use the magic GH_AW_CI_TRIGGER_TOKEN secret as fallback - customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_EXTRA_EMPTY_COMMIT_TOKEN: %s\n", getEffectiveCITriggerGitHubToken(""))) - createPRLog.Print("Extra empty commit using GH_AW_CI_TRIGGER_TOKEN") - } else { - customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_EXTRA_EMPTY_COMMIT_TOKEN: %s\n", extraEmptyCommitToken)) - createPRLog.Printf("Extra empty commit using explicit token") - } + // Add extra empty commit token (for pushing an empty commit to trigger CI) + // Defaults to GH_AW_CI_TRIGGER_TOKEN when not explicitly configured + ciTriggerToken := data.SafeOutputs.CreatePullRequests.GithubTokenForExtraEmptyCommit + if ciTriggerToken == "app" { + customEnvVars = append(customEnvVars, " GH_AW_CI_TRIGGER_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token || '' }}\n") + createPRLog.Print("Extra empty commit using GitHub App token") + } else if ciTriggerToken == "default" || ciTriggerToken == "" { + // Use the magic GH_AW_CI_TRIGGER_TOKEN secret (default behavior when not explicitly configured) + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_CI_TRIGGER_TOKEN: %s\n", getEffectiveCITriggerGitHubToken(""))) + createPRLog.Print("Extra empty commit using GH_AW_CI_TRIGGER_TOKEN") + } else { + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_CI_TRIGGER_TOKEN: %s\n", ciTriggerToken)) + createPRLog.Printf("Extra empty commit using explicit token") } // Add standard environment variables (metadata + staged/target repo) From d12cabb89066abc17d0cd63bbf8bd0ce1166de7f Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 24 Feb 2026 00:10:18 +0000 Subject: [PATCH 2/6] fmt --- pkg/workflow/compiler_safe_outputs_job.go | 7 ++++--- pkg/workflow/create_pull_request.go | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go index 78933948b4..bbc44021a3 100644 --- a/pkg/workflow/compiler_safe_outputs_job.go +++ b/pkg/workflow/compiler_safe_outputs_job.go @@ -416,14 +416,15 @@ func (c *Compiler) buildJobLevelSafeOutputEnvVars(data *WorkflowData, workflowID ciTriggerToken = data.SafeOutputs.PushToPullRequestBranch.GithubTokenForExtraEmptyCommit } - if ciTriggerToken == "app" { + switch ciTriggerToken { + case "app": envVars["GH_AW_CI_TRIGGER_TOKEN"] = "${{ steps.safe-outputs-app-token.outputs.token || '' }}" consolidatedSafeOutputsJobLog.Print("Extra empty commit using GitHub App token") - } else if ciTriggerToken == "default" || ciTriggerToken == "" { + case "default", "": // Use the magic GH_AW_CI_TRIGGER_TOKEN secret (default behavior when not explicitly configured) envVars["GH_AW_CI_TRIGGER_TOKEN"] = getEffectiveCITriggerGitHubToken("") consolidatedSafeOutputsJobLog.Print("Extra empty commit using GH_AW_CI_TRIGGER_TOKEN") - } else { + default: envVars["GH_AW_CI_TRIGGER_TOKEN"] = ciTriggerToken consolidatedSafeOutputsJobLog.Print("Extra empty commit using explicit token") } diff --git a/pkg/workflow/create_pull_request.go b/pkg/workflow/create_pull_request.go index 5afb727d84..d0e6e474e6 100644 --- a/pkg/workflow/create_pull_request.go +++ b/pkg/workflow/create_pull_request.go @@ -168,14 +168,15 @@ func (c *Compiler) buildCreateOutputPullRequestJob(data *WorkflowData, mainJobNa // Add extra empty commit token (for pushing an empty commit to trigger CI) // Defaults to GH_AW_CI_TRIGGER_TOKEN when not explicitly configured ciTriggerToken := data.SafeOutputs.CreatePullRequests.GithubTokenForExtraEmptyCommit - if ciTriggerToken == "app" { + switch ciTriggerToken { + case "app": customEnvVars = append(customEnvVars, " GH_AW_CI_TRIGGER_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token || '' }}\n") createPRLog.Print("Extra empty commit using GitHub App token") - } else if ciTriggerToken == "default" || ciTriggerToken == "" { + case "default", "": // Use the magic GH_AW_CI_TRIGGER_TOKEN secret (default behavior when not explicitly configured) customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_CI_TRIGGER_TOKEN: %s\n", getEffectiveCITriggerGitHubToken(""))) createPRLog.Print("Extra empty commit using GH_AW_CI_TRIGGER_TOKEN") - } else { + default: customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_CI_TRIGGER_TOKEN: %s\n", ciTriggerToken)) createPRLog.Printf("Extra empty commit using explicit token") } From ba94aecd7aa3a5b3854ed1f94f78ff7e34040edc Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 24 Feb 2026 00:33:10 +0000 Subject: [PATCH 3/6] add tests --- ...eate_pull_request_ci_trigger_token_test.go | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 pkg/workflow/create_pull_request_ci_trigger_token_test.go diff --git a/pkg/workflow/create_pull_request_ci_trigger_token_test.go b/pkg/workflow/create_pull_request_ci_trigger_token_test.go new file mode 100644 index 0000000000..12ed1723bd --- /dev/null +++ b/pkg/workflow/create_pull_request_ci_trigger_token_test.go @@ -0,0 +1,216 @@ +//go:build !integration + +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/github/gh-aw/pkg/stringutil" + "github.com/github/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCreatePullRequestCITriggerToken verifies the GH_AW_CI_TRIGGER_TOKEN env var +// is correctly generated in the safe_outputs job for different configurations: +// - unset/empty: uses secrets.GH_AW_CI_TRIGGER_TOKEN +// - "app": uses steps.safe-outputs-app-token.outputs.token +// - explicit token: uses the specified token value +func TestCreatePullRequestCITriggerToken(t *testing.T) { + tests := []struct { + name string + tokenConfig string // value for github-token-for-extra-empty-commit + expectedContains string // expected substring in GH_AW_CI_TRIGGER_TOKEN env var + notExpected string // should NOT contain this string + }{ + { + name: "unset config uses secrets.GH_AW_CI_TRIGGER_TOKEN", + tokenConfig: "", + expectedContains: "${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}", + notExpected: "safe-outputs-app-token", + }, + { + name: "default config uses secrets.GH_AW_CI_TRIGGER_TOKEN", + tokenConfig: "default", + expectedContains: "${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}", + notExpected: "safe-outputs-app-token", + }, + { + name: "app config uses app token step output", + tokenConfig: "app", + expectedContains: "${{ steps.safe-outputs-app-token.outputs.token || '' }}", + notExpected: "secrets.GH_AW_CI_TRIGGER_TOKEN", + }, + { + name: "explicit token uses provided value", + tokenConfig: "${{ secrets.MY_CUSTOM_PAT }}", + expectedContains: "${{ secrets.MY_CUSTOM_PAT }}", + notExpected: "safe-outputs-app-token", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := testutil.TempDir(t, "ci-trigger-token-test") + + // Build the workflow content with or without the token config + var safeOutputsConfig string + if tt.tokenConfig == "" { + safeOutputsConfig = `safe-outputs: + create-pull-request: + title-prefix: "[test] " + labels: [test]` + } else { + safeOutputsConfig = `safe-outputs: + create-pull-request: + title-prefix: "[test] " + labels: [test] + github-token-for-extra-empty-commit: ` + tt.tokenConfig + } + + testContent := `--- +on: push +permissions: + contents: read + pull-requests: write + issues: read +tools: + github: + allowed: [list_issues] +engine: claude +features: + dangerous-permissions-write: true +strict: false +` + safeOutputsConfig + ` +--- + +# Test CI Trigger Token Configuration + +This workflow tests the GH_AW_CI_TRIGGER_TOKEN env var generation. +` + + testFile := filepath.Join(tmpDir, "test-ci-trigger-token.md") + err := os.WriteFile(testFile, []byte(testContent), 0644) + require.NoError(t, err, "Failed to write test file") + + compiler := NewCompiler() + + err = compiler.CompileWorkflow(testFile) + require.NoError(t, err, "Should compile workflow without error") + + lockFile := stringutil.MarkdownToLockFile(testFile) + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err, "Should read generated lock file") + + lockContentStr := string(lockContent) + + // Verify the expected token configuration is present + assert.Contains(t, lockContentStr, "GH_AW_CI_TRIGGER_TOKEN:", + "Generated workflow should contain GH_AW_CI_TRIGGER_TOKEN env var") + + assert.Contains(t, lockContentStr, tt.expectedContains, + "GH_AW_CI_TRIGGER_TOKEN should have expected value") + + if tt.notExpected != "" { + // Find the GH_AW_CI_TRIGGER_TOKEN line and verify it doesn't contain the unexpected value + lines := strings.Split(lockContentStr, "\n") + for _, line := range lines { + if strings.Contains(line, "GH_AW_CI_TRIGGER_TOKEN:") { + assert.NotContains(t, line, tt.notExpected, + "GH_AW_CI_TRIGGER_TOKEN should not contain %q", tt.notExpected) + } + } + } + }) + } +} + +// TestPushToPullRequestBranchCITriggerToken verifies the GH_AW_CI_TRIGGER_TOKEN env var +// is correctly generated for push-to-pull-request-branch safe output configuration. +func TestPushToPullRequestBranchCITriggerToken(t *testing.T) { + tests := []struct { + name string + tokenConfig string + expectedContains string + }{ + { + name: "unset config uses secrets.GH_AW_CI_TRIGGER_TOKEN", + tokenConfig: "", + expectedContains: "${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}", + }, + { + name: "app config uses app token step output", + tokenConfig: "app", + expectedContains: "${{ steps.safe-outputs-app-token.outputs.token || '' }}", + }, + { + name: "explicit token uses provided value", + tokenConfig: "${{ secrets.CUSTOM_PUSH_TOKEN }}", + expectedContains: "${{ secrets.CUSTOM_PUSH_TOKEN }}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := testutil.TempDir(t, "push-pr-branch-ci-trigger-test") + + var safeOutputsConfig string + if tt.tokenConfig == "" { + safeOutputsConfig = `safe-outputs: + push-to-pull-request-branch: + labels: [test]` + } else { + safeOutputsConfig = `safe-outputs: + push-to-pull-request-branch: + labels: [test] + github-token-for-extra-empty-commit: ` + tt.tokenConfig + } + + testContent := `--- +on: + pull_request: + types: [opened] +permissions: + contents: read + pull-requests: write +tools: + github: + allowed: [list_issues] +engine: claude +features: + dangerous-permissions-write: true +strict: false +` + safeOutputsConfig + ` +--- + +# Test Push to PR Branch CI Trigger Token + +This workflow tests push-to-pull-request-branch token configuration. +` + + testFile := filepath.Join(tmpDir, "test-push-pr-branch-token.md") + err := os.WriteFile(testFile, []byte(testContent), 0644) + require.NoError(t, err, "Failed to write test file") + + compiler := NewCompiler() + + err = compiler.CompileWorkflow(testFile) + require.NoError(t, err, "Should compile workflow without error") + + lockFile := stringutil.MarkdownToLockFile(testFile) + lockContent, err := os.ReadFile(lockFile) + require.NoError(t, err, "Should read generated lock file") + + lockContentStr := string(lockContent) + + assert.Contains(t, lockContentStr, "GH_AW_CI_TRIGGER_TOKEN:", + "Generated workflow should contain GH_AW_CI_TRIGGER_TOKEN env var") + + assert.Contains(t, lockContentStr, tt.expectedContains, + "GH_AW_CI_TRIGGER_TOKEN should have expected value") + }) + } +} From 333f8abdba55d1de3ac85a8d31569979c3dd2e06 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 24 Feb 2026 00:35:08 +0000 Subject: [PATCH 4/6] Update actions/setup/js/extra_empty_commit.cjs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/setup/js/extra_empty_commit.cjs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/extra_empty_commit.cjs b/actions/setup/js/extra_empty_commit.cjs index 130ad4f25d..6819bf8b5b 100644 --- a/actions/setup/js/extra_empty_commit.cjs +++ b/actions/setup/js/extra_empty_commit.cjs @@ -9,10 +9,12 @@ * GITHUB_TOKEN do not trigger other workflow runs. * * The token comes from `github-token-for-extra-empty-commit` in safe-outputs config - * (passed as GH_AW_CI_TRIGGER_TOKEN env var). Supported values: - * - `app` - Use GitHub App token from safe-outputs-app-token step - * - `default` - Use the magic secret GH_AW_CI_TRIGGER_TOKEN - * - `${{ secrets.CUSTOM_TOKEN }}` - Use a custom PAT or secret + * and is passed in as the GH_AW_CI_TRIGGER_TOKEN environment variable. + * By the time this script runs, GH_AW_CI_TRIGGER_TOKEN must contain an actual + * GitHub authentication token (for example, a GitHub App token or a PAT). + * Any selection or defaulting behavior (such as resolving `app`, `default`, + * or a specific secret reference) is handled in the workflow compiler/config + * layer before this script is invoked. */ /** From d5bc10e64ba1f4676df13030e48be992f732398f Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 24 Feb 2026 00:36:27 +0000 Subject: [PATCH 5/6] go fmt --- ...eate_pull_request_ci_trigger_token_test.go | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/pkg/workflow/create_pull_request_ci_trigger_token_test.go b/pkg/workflow/create_pull_request_ci_trigger_token_test.go index 12ed1723bd..d71734882d 100644 --- a/pkg/workflow/create_pull_request_ci_trigger_token_test.go +++ b/pkg/workflow/create_pull_request_ci_trigger_token_test.go @@ -21,34 +21,34 @@ import ( // - explicit token: uses the specified token value func TestCreatePullRequestCITriggerToken(t *testing.T) { tests := []struct { - name string - tokenConfig string // value for github-token-for-extra-empty-commit - expectedContains string // expected substring in GH_AW_CI_TRIGGER_TOKEN env var - notExpected string // should NOT contain this string + name string + tokenConfig string // value for github-token-for-extra-empty-commit + expectedContains string // expected substring in GH_AW_CI_TRIGGER_TOKEN env var + notExpected string // should NOT contain this string }{ { - name: "unset config uses secrets.GH_AW_CI_TRIGGER_TOKEN", - tokenConfig: "", - expectedContains: "${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}", - notExpected: "safe-outputs-app-token", + name: "unset config uses secrets.GH_AW_CI_TRIGGER_TOKEN", + tokenConfig: "", + expectedContains: "${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}", + notExpected: "safe-outputs-app-token", }, { - name: "default config uses secrets.GH_AW_CI_TRIGGER_TOKEN", - tokenConfig: "default", - expectedContains: "${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}", - notExpected: "safe-outputs-app-token", + name: "default config uses secrets.GH_AW_CI_TRIGGER_TOKEN", + tokenConfig: "default", + expectedContains: "${{ secrets.GH_AW_CI_TRIGGER_TOKEN }}", + notExpected: "safe-outputs-app-token", }, { - name: "app config uses app token step output", - tokenConfig: "app", - expectedContains: "${{ steps.safe-outputs-app-token.outputs.token || '' }}", - notExpected: "secrets.GH_AW_CI_TRIGGER_TOKEN", + name: "app config uses app token step output", + tokenConfig: "app", + expectedContains: "${{ steps.safe-outputs-app-token.outputs.token || '' }}", + notExpected: "secrets.GH_AW_CI_TRIGGER_TOKEN", }, { - name: "explicit token uses provided value", - tokenConfig: "${{ secrets.MY_CUSTOM_PAT }}", - expectedContains: "${{ secrets.MY_CUSTOM_PAT }}", - notExpected: "safe-outputs-app-token", + name: "explicit token uses provided value", + tokenConfig: "${{ secrets.MY_CUSTOM_PAT }}", + expectedContains: "${{ secrets.MY_CUSTOM_PAT }}", + notExpected: "safe-outputs-app-token", }, } From b215e35704b1dc9f1982368d22c12a213df82557 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Tue, 24 Feb 2026 00:50:39 +0000 Subject: [PATCH 6/6] go fmt --- pkg/workflow/create_pull_request_ci_trigger_token_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/workflow/create_pull_request_ci_trigger_token_test.go b/pkg/workflow/create_pull_request_ci_trigger_token_test.go index d71734882d..ca1fc35c15 100644 --- a/pkg/workflow/create_pull_request_ci_trigger_token_test.go +++ b/pkg/workflow/create_pull_request_ci_trigger_token_test.go @@ -116,8 +116,7 @@ This workflow tests the GH_AW_CI_TRIGGER_TOKEN env var generation. if tt.notExpected != "" { // Find the GH_AW_CI_TRIGGER_TOKEN line and verify it doesn't contain the unexpected value - lines := strings.Split(lockContentStr, "\n") - for _, line := range lines { + for line := range strings.SplitSeq(lockContentStr, "\n") { if strings.Contains(line, "GH_AW_CI_TRIGGER_TOKEN:") { assert.NotContains(t, line, tt.notExpected, "GH_AW_CI_TRIGGER_TOKEN should not contain %q", tt.notExpected)