@@ -89,3 +89,97 @@ This workflow tests that duplicate checkout steps are properly deduplicated.
8989
9090 t .Logf ("✓ Duplicate step validation working correctly: found %d checkout step(s) in safe_outputs job (deduplicated)" , checkoutCount )
9191}
92+
93+ // TestDuplicateStepValidation_CheckoutPlusGitHubApp_Integration tests that combining
94+ // a top-level github-app with multiple cross-repo checkouts and tools.github does not
95+ // produce duplicate 'Generate GitHub App token' steps in the activation job.
96+ //
97+ // When multiple checkout entries all fall back to the top-level github-app,
98+ // each minting step previously received the same name, triggering the duplicate
99+ // step validation error ("compiler bug: duplicate step 'Generate GitHub App token'").
100+ func TestDuplicateStepValidation_CheckoutPlusGitHubApp_Integration (t * testing.T ) {
101+ tmpDir := testutil .TempDir (t , "duplicate-checkout-token-test" )
102+
103+ // Workflow that combines all three conditions that triggered the bug:
104+ // 1. Top-level github-app: (used as fallback for all token-minting operations)
105+ // 2. Two cross-repo checkout: entries (both fall back to the top-level github-app)
106+ // 3. tools.github: with mode: remote
107+ mdContent := `---
108+ on:
109+ issues:
110+ types: [opened]
111+ engine: claude
112+ strict: false
113+ permissions:
114+ contents: read
115+ issues: read
116+ pull-requests: read
117+
118+ github-app:
119+ app-id: ${{ secrets.APP_ID }}
120+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
121+ repositories: ["side-repo", "target-repo"]
122+
123+ checkout:
124+ - repository: myorg/target-repo
125+ ref: main
126+ - repository: myorg/side-repo
127+ ref: main
128+
129+ tools:
130+ github:
131+ mode: remote
132+ toolsets: [default]
133+ ---
134+
135+ # Test Workflow
136+
137+ This workflow tests that multiple checkouts + top-level github-app + tools.github
138+ compile without duplicate 'Generate GitHub App token' step errors in the activation job.
139+ `
140+
141+ mdFile := filepath .Join (tmpDir , "test-checkout-github-app.md" )
142+ err := os .WriteFile (mdFile , []byte (mdContent ), 0644 )
143+ if err != nil {
144+ t .Fatalf ("Failed to create test file: %v" , err )
145+ }
146+
147+ // Compile workflow — must succeed so the generated lock file can be validated.
148+ compiler := NewCompiler ()
149+ err = compiler .CompileWorkflow (mdFile )
150+ if err != nil {
151+ if strings .Contains (err .Error (), "duplicate step" ) {
152+ t .Fatalf ("Regression: duplicate step error when combining multiple checkouts + top-level github-app: %v" , err )
153+ }
154+ t .Fatalf ("Compilation failed unexpectedly before lock-file assertions could run: %v" , err )
155+ }
156+
157+ // Read the generated lock file and verify the activation job has unique step names
158+ lockFile := stringutil .MarkdownToLockFile (mdFile )
159+ lockContent , err := os .ReadFile (lockFile )
160+ if err != nil {
161+ t .Fatalf ("Failed to read lock file: %v" , err )
162+ }
163+ lockContentStr := string (lockContent )
164+
165+ // Both checkout token minting steps should be present with unique names.
166+ // The step names are "Generate GitHub App token for checkout (N)" — one per checkout entry.
167+ count0 := strings .Count (lockContentStr , "name: Generate GitHub App token for checkout (0)" )
168+ count1 := strings .Count (lockContentStr , "name: Generate GitHub App token for checkout (1)" )
169+ if count0 != 1 {
170+ t .Errorf ("Expected exactly 1 'Generate GitHub App token for checkout (0)' step, got %d" , count0 )
171+ }
172+ if count1 != 1 {
173+ t .Errorf ("Expected exactly 1 'Generate GitHub App token for checkout (1)' step, got %d" , count1 )
174+ }
175+
176+ // Exactly one generic "Generate GitHub App token" step is expected — for the GitHub MCP server
177+ // in the agent job (id: github-mcp-app-token). If more than one appears, that means a
178+ // checkout minting step was not renamed, which would cause a duplicate-name error.
179+ genericCount := strings .Count (lockContentStr , "name: Generate GitHub App token\n " )
180+ if genericCount > 1 {
181+ t .Errorf ("Found %d generic 'Generate GitHub App token' steps; checkout steps must use unique names to avoid duplicates" , genericCount )
182+ }
183+
184+ t .Logf ("✓ No duplicate token steps: checkout (0) count=%d, checkout (1) count=%d, generic=%d" , count0 , count1 , genericCount )
185+ }
0 commit comments