@@ -183,33 +183,78 @@ func isSlashCommandWorkflow(on string) bool {
183183 return strings .Contains (on , "slash_command" )
184184}
185185
186+ // entityConcurrencyKey builds a ${{ ... }} concurrency-group expression for entity-number
187+ // based workflows. primaryParts are the event-number identifiers (e.g.,
188+ // "github.event.pull_request.number"), tailParts are the trailing fallbacks (e.g.,
189+ // "github.ref", "github.run_id"). When hasItemNumber is true, "inputs.item_number" is
190+ // inserted between the primary identifiers and the tail, providing a stable per-item
191+ // key for manual workflow_dispatch runs triggered via the label trigger shorthand.
192+ func entityConcurrencyKey (primaryParts []string , tailParts []string , hasItemNumber bool ) string {
193+ parts := make ([]string , 0 , len (primaryParts )+ len (tailParts )+ 1 )
194+ parts = append (parts , primaryParts ... )
195+ if hasItemNumber {
196+ parts = append (parts , "inputs.item_number" )
197+ }
198+ parts = append (parts , tailParts ... )
199+ return "${{ " + strings .Join (parts , " || " ) + " }}"
200+ }
201+
186202// buildConcurrencyGroupKeys builds an array of keys for the concurrency group
187203func buildConcurrencyGroupKeys (workflowData * WorkflowData , isCommandTrigger bool ) []string {
188204 keys := []string {"gh-aw" , "${{ github.workflow }}" }
189205
206+ // Whether this workflow exposes inputs.item_number via workflow_dispatch (label trigger shorthand).
207+ // When true, include it in the concurrency key so that manual dispatches for different items
208+ // use distinct groups and don't cancel each other.
209+ hasItemNumber := workflowData .HasDispatchItemNumber
210+
190211 if isCommandTrigger || isSlashCommandWorkflow (workflowData .On ) {
191212 // For command/slash_command workflows: use issue/PR number; fall back to run_id when
192213 // neither is available (e.g. manual workflow_dispatch of the outer workflow).
193214 keys = append (keys , "${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}" )
194215 } else if isPullRequestWorkflow (workflowData .On ) && isIssueWorkflow (workflowData .On ) {
195216 // Mixed workflows with both issue and PR triggers
196- keys = append (keys , "${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}" )
217+ keys = append (keys , entityConcurrencyKey (
218+ []string {"github.event.issue.number" , "github.event.pull_request.number" },
219+ []string {"github.run_id" },
220+ hasItemNumber ,
221+ ))
197222 } else if isPullRequestWorkflow (workflowData .On ) && isDiscussionWorkflow (workflowData .On ) {
198223 // Mixed workflows with PR and discussion triggers
199- keys = append (keys , "${{ github.event.pull_request.number || github.event.discussion.number || github.run_id }}" )
224+ keys = append (keys , entityConcurrencyKey (
225+ []string {"github.event.pull_request.number" , "github.event.discussion.number" },
226+ []string {"github.run_id" },
227+ hasItemNumber ,
228+ ))
200229 } else if isIssueWorkflow (workflowData .On ) && isDiscussionWorkflow (workflowData .On ) {
201230 // Mixed workflows with issue and discussion triggers
202- keys = append (keys , "${{ github.event.issue.number || github.event.discussion.number || github.run_id }}" )
231+ keys = append (keys , entityConcurrencyKey (
232+ []string {"github.event.issue.number" , "github.event.discussion.number" },
233+ []string {"github.run_id" },
234+ hasItemNumber ,
235+ ))
203236 } else if isPullRequestWorkflow (workflowData .On ) {
204237 // PR workflows: use PR number, fall back to ref then run_id
205- keys = append (keys , "${{ github.event.pull_request.number || github.ref || github.run_id }}" )
238+ keys = append (keys , entityConcurrencyKey (
239+ []string {"github.event.pull_request.number" },
240+ []string {"github.ref" , "github.run_id" },
241+ hasItemNumber ,
242+ ))
206243 } else if isIssueWorkflow (workflowData .On ) {
207244 // Issue workflows: run_id is the fallback when no issue context is available
208245 // (e.g. when a mixed-trigger workflow is started via workflow_dispatch).
209- keys = append (keys , "${{ github.event.issue.number || github.run_id }}" )
246+ keys = append (keys , entityConcurrencyKey (
247+ []string {"github.event.issue.number" },
248+ []string {"github.run_id" },
249+ hasItemNumber ,
250+ ))
210251 } else if isDiscussionWorkflow (workflowData .On ) {
211252 // Discussion workflows: run_id is the fallback when no discussion context is available.
212- keys = append (keys , "${{ github.event.discussion.number || github.run_id }}" )
253+ keys = append (keys , entityConcurrencyKey (
254+ []string {"github.event.discussion.number" },
255+ []string {"github.run_id" },
256+ hasItemNumber ,
257+ ))
213258 } else if isPushWorkflow (workflowData .On ) {
214259 // Push workflows: use ref to differentiate between branches
215260 keys = append (keys , "${{ github.ref || github.run_id }}" )
0 commit comments