GitHub Agentic Workflows

Concurrency Control

GitHub Agentic Workflows uses dual-level concurrency control to prevent resource exhaustion and ensure predictable execution:

  • Per-workflow: Limits based on workflow name and trigger context (issue, PR, branch)
  • Per-engine: Limits AI execution across all workflows via engine.concurrency

Workflow-level concurrency groups include the workflow name plus context-specific identifiers:

Trigger TypeConcurrency GroupCancel In Progress
Issuesgh-aw-${{ github.workflow }}-${{ issue.number }}No
Pull Requestsgh-aw-${{ github.workflow }}-${{ pr.number || ref }}Yes (new commits cancel outdated runs)
Pushgh-aw-${{ github.workflow }}-${{ github.ref }}No
Schedule/Othergh-aw-${{ github.workflow }}No
Label-triggered (label trigger shorthand or label_command)gh-aw-${{ github.workflow }}-${{ entity.number }}-${{ github.event.label.name }}Yes for PRs, No otherwise

This ensures workflows on different issues, PRs, or branches run concurrently without interference.

The default per-engine pattern gh-aw-{engine-id} ensures only one agent job runs per engine across all workflows, preventing AI resource exhaustion. The group includes only the engine ID and gh-aw- prefix — workflow name, issue/PR numbers, and branches are excluded.

jobs:
agent:
concurrency:
group: "gh-aw-{engine-id}"

Override either level independently:

---
on: push
concurrency: # Workflow-level
group: custom-group-${{ github.ref }}
cancel-in-progress: true
engine:
id: copilot
concurrency: # Engine-level
group: "gh-aw-copilot-${{ github.workflow }}"
tools:
github:
allowed: [list_issues]
---

The safe_outputs job runs independently from the agent job and can process outputs concurrently across workflow runs. Use safe-outputs.concurrency-group to serialize access when needed:

safe-outputs:
concurrency-group: "safe-outputs-${{ github.repository }}"
create-issue:

When set, the safe_outputs job uses cancel-in-progress: false — meaning queued runs wait for the in-progress run to finish rather than being cancelled. This is useful for workflows that create issues or pull requests where duplicate operations would be undesirable.

See Safe Outputs for details.

GitHub Actions concurrency groups accept an optional queue field that controls how multiple pending runs in the same group are handled. The gh-aw compiler preserves this field in both top-level and per-engine concurrency blocks:

ValueBehavior
single (Actions default)Only the latest pending run is kept; earlier pending runs are discarded.
maxAll pending runs queue and run in arrival order.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
queue: max

Compiler-generated concurrency groups (agent, output, and conclusion jobs) emit queue: max by default so back-to-back triggers run sequentially rather than being dropped. Set features.group-concurrency-queue: false to omit queue from generated groups and revert to the Actions default:

features:
group-concurrency-queue: false

The conclusion job — which handles reporting and post-agent cleanup — automatically receives a workflow-specific concurrency group derived from the workflow filename:

conclusion:
concurrency:
group: "gh-aw-conclusion-my-workflow"
cancel-in-progress: false
queue: max

This prevents conclusion jobs from colliding when multiple agents run the same workflow concurrently. The group uses cancel-in-progress: false so queued conclusion runs complete in order rather than being discarded, and queue: max preserves arrival order for queued runs (see Queue Behavior).

This concurrency group is set automatically during compilation and requires no manual configuration.

When concurrency.job-discriminator is set, the discriminator is also appended to the conclusion job’s concurrency group, making each run’s group distinct:

concurrency:
job-discriminator: ${{ github.event.issue.number || github.run_id }}

This generates a group like gh-aw-conclusion-my-workflow-${{ github.event.issue.number || github.run_id }}, preventing concurrent runs for different issues or inputs from competing for the same conclusion slot.

When multiple workflow instances are dispatched concurrently with different inputs (fan-out pattern), compiler-generated job-level concurrency groups are static across all runs — causing all but the latest dispatched run to be cancelled as they compete for the same slot.

Use concurrency.job-discriminator to append a unique expression to compiler-generated job-level concurrency groups (agent, output, and conclusion jobs), making each dispatched run’s group distinct:

concurrency:
job-discriminator: ${{ inputs.finding_id }}

This generates a unique job-level concurrency group per dispatched run, preventing fan-out cancellations while preserving the per-workflow concurrency group at the workflow level.

Common expressions:

ScenarioExpression
Fan-out by a specific input${{ inputs.finding_id }}
Universal uniqueness (e.g. scheduled runs)${{ github.run_id }}
Dispatched or scheduled fallback${{ inputs.organization || github.run_id }}