Skip to content

Bug: Per-engine job concurrency blocks workflow_dispatch issue workflows from running in parallel #18101

@benvillalobos

Description

@benvillalobos

Summary

The default per-engine job-level concurrency group (gh-aw-{engine-id}-${{ github.workflow }}) prevents workflow_dispatch-triggered issue workflows from processing different issues simultaneously. Two runs of the same workflow for different issues are serialized at the agent and detection job level, even though they operate on completely independent data.

Reproduction

  1. Create an issue-triage workflow triggered via workflow_dispatch (not on: issues:):
on:
    workflow_dispatch:
        inputs:
            issue_number:
                required: true
concurrency:
    group: issue-triage-${{ github.event.inputs.issue_number }}`
    cancel-in-progress: true
engine:
    id: copilot
  1. Compile with gh aw compile
  2. Dispatch two runs simultaneously for different issues (e.g., #277977 and #295537)
  3. Expected: Both run in parallel since they process different issues
  4. Actual: The second run's agent job is queued, waiting for the first to complete

Root Cause

In pkg/workflow/concurrency.go GenerateJobConcurrencyConfig():

func GenerateJobConcurrencyConfig(workflowData *WorkflowData) string {
    // ...
    if hasSpecialTriggers(workflowData) {
        return ""  // Skip for issues, PRs, push, etc.
    }
    // For generic triggers like workflow_dispatch, apply default concurrency
    groupValue := fmt.Sprintf("gh-aw-%s-${{ github.workflow }}", engineID)
    // ...
}

hasSpecialTriggers() checks workflowData.On for substrings like "issues", "pull_request", etc. But workflows that use workflow_dispatch to process issues (common for triage workflows triggered by other workflows) don't contain these substrings, so they fall through to the default engine-scoped concurrency.

The compiled lock file gets:

jobs:
  agent:
    concurrency:
      group: "gh-aw-copilot-${{ github.workflow }}"  # Shared across ALL runs
  detection:
    concurrency:
      group: "gh-aw-copilot-${{ github.workflow }}"  # Same group

Meanwhile, the workflow-level concurrency is correctly per-issue:

concurrency:
  group: issue-triage-${{ github.event.inputs.repo_owner }}-${{ github.event.inputs.repo_name }}-${{ github.event.inputs.issue_number }}`
  cancel-in-progress: true

Evidence

Two runs on bv/triage-aw2 branch for different issues, dispatched simultaneously:

Run Issue agent job detection job Notes
22342722820 #277977 ✅ ran first ✅ ran first Completed normally
22342722831 #295537 ⏳ queued ~6min ⏳ queued Waited for #277977's agent/detection to finish

Both eventually succeeded, but #295537 was unnecessarily delayed by ~6 minutes waiting in the concurrency queue.

Interaction with workflow-level concurrency

The workflow-level concurrency (from the .md frontmatter) correctly includes the issue number. When both levels are set:

  • Workflow-level cancel-in-progress: true cancels runs for the same issue → correct
  • Job-level serialization blocks runs for different issues → incorrect for this use case

The user's intent (expressed via concurrency in the .md) is per-issue isolation. The compiler-injected job-level group overrides this for the agent/detection jobs.

Suggested Fixes

Option A: Inherit workflow-level concurrency context

When the user has set a custom workflow-level concurrency group, the job-level default should incorporate the same context variables. If the workflow concurrency group includes issue_number, the job group should too.

Option B: Skip job concurrency when workflow concurrency is set

If the user explicitly defined concurrency in their .md frontmatter, don't inject the default per-engine job concurrency — the user has already expressed their concurrency intent.

Option C: Allow engine.concurrency to suppress the default

Document that users can set engine.concurrency: none (or similar) to opt out of the default job-level serialization.

Workaround (current)

Users can manually edit the .lock.yml to remove the concurrency: blocks from agent and detection jobs, but this is lost on recompile.

Alternatively, set engine.concurrency in the .md frontmatter to include the issue number:

engine:
    id: copilot
    concurrency:
        group: "gh-aw-copilot-${{ github.workflow }}-${{ github.event.inputs.issue_number }}"

Environment

  • gh-aw compiler: v0.50.0
  • Copilot CLI: 0.0.415
  • Workflow: issue-triage.md in microsoft/vscode-engineering

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions