Conversation
…-level concurrency groups Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
concurrency.job-discriminator to prevent fan-out cancellations in job-level concurrency groups
|
@copilot make the group field non-required so that the job-discriminator can be used without having to specify the group |
…minator standalone Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in 4d82b6b. Removed concurrency:
job-discriminator: ${{ inputs.finding_id }}When Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot add more examples in test. |
…fier and slide-deck-maintainer workflows Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in 7de7af0. More test examples (
Applied to existing workflows:
|
There was a problem hiding this comment.
Pull request overview
Adds a concurrency.job-discriminator frontmatter field to prevent job-level concurrency group collisions in fan-out dispatch patterns (especially for schedule workflows), while keeping compiled lock files valid GitHub Actions YAML by stripping the extension field.
Changes:
- Introduces
concurrency.job-discriminatorextraction/validation and appends it to compiler-generated job-level concurrency groups. - Updates the main workflow JSON schema to allow
job-discriminator(and makesgroupoptional). - Applies the new field to existing workflows and updates lock files; adds unit tests covering extraction and group generation behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/concurrency.go | Appends ConcurrencyJobDiscriminator to default job-level concurrency group generation. |
| pkg/workflow/concurrency_test.go | Adds test cases for discriminator behavior across triggers and engines. |
| pkg/workflow/compiler_types.go | Adds ConcurrencyJobDiscriminator to WorkflowData. |
| pkg/workflow/compiler_orchestrator_workflow.go | Extracts job-discriminator from raw frontmatter and strips it from serialized workflow-level concurrency YAML. |
| pkg/workflow/compiler_orchestrator_workflow_test.go | Adds tests for discriminator extraction and stripping behavior. |
| pkg/workflow/compiler.go | Validates concurrency.job-discriminator expressions during compilation. |
| pkg/parser/schemas/main_workflow_schema.json | Extends concurrency schema with job-discriminator and makes group optional. |
| .github/workflows/stale-repo-identifier.md | Uses concurrency.job-discriminator to avoid fan-out cancellations. |
| .github/workflows/stale-repo-identifier.lock.yml | Reflects updated compiled job concurrency group with discriminator appended. |
| .github/workflows/slide-deck-maintainer.md | Uses concurrency.job-discriminator to avoid fan-out cancellations. |
| .github/workflows/slide-deck-maintainer.lock.yml | Reflects updated compiled job concurrency group with discriminator appended. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| if workflowData.ConcurrencyJobDiscriminator != "" { | ||
| concurrencyLog.Printf("Appending job discriminator to job-level concurrency group: %s", workflowData.ConcurrencyJobDiscriminator) | ||
| groupValue = fmt.Sprintf("%s-%s", groupValue, workflowData.ConcurrencyJobDiscriminator) | ||
| } | ||
| concurrencyConfig := fmt.Sprintf("concurrency:\n group: \"%s\"", groupValue) |
There was a problem hiding this comment.
job-discriminator can contain valid GitHub expressions that include double quotes or escaped newlines (e.g. ${{ github.event_name == "push" }}), which will break the generated YAML because the group is always wrapped in unescaped double quotes. Prefer emitting the YAML scalar using proper escaping (e.g. %q / YAML marshalling) and/or add validation to reject \n/\r in the discriminator to guarantee the compiled workflow YAML remains valid.
| // When job-discriminator is the only field, there is no user-specified workflow-level | ||
| // group to emit; return empty so the compiler can generate the default concurrency. | ||
| if len(cleanMap) == 0 { | ||
| return "" | ||
| } | ||
| // Use a minimal temporary frontmatter containing only the concurrency key to avoid | ||
| // copying the entire (potentially large) frontmatter map. | ||
| return c.extractTopLevelYAMLSection(map[string]any{"concurrency": cleanMap}, "concurrency") | ||
| } |
There was a problem hiding this comment.
After stripping job-discriminator, this function can still serialize a concurrency object that has cancel-in-progress (or other fields) but no group (e.g. frontmatter concurrency: { cancel-in-progress: true, job-discriminator: ... }). GitHub Actions requires group in the object form, so this would generate invalid lock file YAML. Consider explicitly detecting cleanMap["group"] missing and returning an error (preferred) or returning empty string / dropping the remaining fields so the compiler generates a valid default concurrency group.
| if workflowData.ConcurrencyJobDiscriminator != "" { | ||
| if err := validateConcurrencyGroupExpression(workflowData.ConcurrencyJobDiscriminator); err != nil { | ||
| return formatCompilerError(markdownPath, "error", "concurrency.job-discriminator validation failed: "+err.Error(), err) | ||
| } | ||
| } |
There was a problem hiding this comment.
With concurrency.group no longer required by the schema, workflowData.Concurrency can now be present but contain no group (e.g. only cancel-in-progress). extractConcurrencyGroupFromYAML will return empty string, so the current validation silently accepts an invalid GitHub Actions concurrency object. Add a guard here to fail validation when workflowData.Concurrency is non-empty but no group can be extracted, and/or explicitly validate that cancel-in-progress is never set without group.
| } | ||
| }, | ||
| "required": ["group"], | ||
| "required": [], | ||
| "examples": [ |
There was a problem hiding this comment.
Setting required: [] allows invalid concurrency objects like {} or { "cancel-in-progress": true } to pass schema validation, but GitHub Actions requires group when using the object form. Update this schema to require at least one of group or job-discriminator (e.g. via anyOf with separate required clauses), and also require group whenever cancel-in-progress is present.
Document the new `job-discriminator` field added in #20190, which prevents fan-out cancellations when multiple workflow instances are dispatched concurrently with different inputs. - Added Fan-Out Concurrency section with examples to concurrency.md - Added job-discriminator field to frontmatter-full.md reference Merged after today's DDUw run (14:26 UTC vs 04:28 UTC).
When multiple workflow instances are dispatched concurrently with different inputs (fan-out pattern), compiler-generated job-level concurrency groups (
gh-aw-{engine}-${{ github.workflow }}) are static across all runs — causing all but 2 of N dispatched runs to be cancelled as they compete for the same slot.Changes
job-discriminatorfield in theconcurrencyfrontmatter block: when set, the expression is appended to compiler-generated job-level concurrency groups (agent,outputjobs), making each run's group uniquejob-discriminatorproperty to the concurrency object inmain_workflow_schema.json; thegroupfield is now optional sojob-discriminatorcan be used standaloneextractConcurrencySection()stripsjob-discriminatorfrom the serialized lock file YAML (it's a gh-aw extension, not valid GitHub Actions);extractConcurrencyJobDiscriminator()reads it from raw frontmatter without modifying the original mapjob-discriminatorexpression is validated alongside other concurrency expressionsworkflow_dispatch-only,push,pull_request) or when engine provides explicit concurrencystale-repo-identifier.mdandslide-deck-maintainer.mdnow usejob-discriminatorto allow concurrent fan-out runs with different inputsUsage
job-discriminatorcan be used with or without an explicitgroup:All forms compile to job-level groups that are unique per run:
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.