feat: add label-command trigger (On Label Command)#21118
feat: add label-command trigger (On Label Command)#21118
Conversation
Co-authored-by: pelikhan <[email protected]>
…fy owner/repo extraction Co-authored-by: pelikhan <[email protected]>
|
Enable label command on "cloclo" agentic workflow and smoke-copilot for "smoke" |
|
@copilot Enable label command on "cloclo" agentic workflow and smoke-copilot for "smoke" |
There was a problem hiding this comment.
Pull request overview
Adds a new label_command workflow trigger that activates on label application (issues/PRs/discussions), generates appropriate on: events + workflow_dispatch, and removes the triggering label at workflow start so it can be re-applied.
Changes:
- Extend frontmatter parsing/schema and compiler defaults to support
label_command(including shorthandon: "label-command <name>"). - Generate label-based
on:blocks (issues/pull_request/discussionwithtypes: [labeled]) plusworkflow_dispatchwithitem_number. - Add activation-job support to remove the triggering label via a new
remove_trigger_label.cjsgithub-script helper, and expose alabel_commandoutput.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/tools.go | Detects label-command workflows; generates on: events and if: condition; adjusts concurrency handling. |
| pkg/workflow/schedule_preprocessing.go | Adds label-command <name> shorthand expansion in on: string preprocessing. |
| pkg/workflow/label_command_test.go | New unit tests for shorthand expansion, event filtering, condition building, and compilation output. |
| pkg/workflow/label_command_parser.go | Adds shorthand expansion helper for label_command. |
| pkg/workflow/label_command.go | Adds supported event filtering + label-command condition builder. |
| pkg/workflow/frontmatter_extraction_yaml.go | Extracts on.label_command config (name/names + events). |
| pkg/workflow/compiler_types.go | Adds LabelCommand* fields to WorkflowData. |
| pkg/workflow/compiler_safe_outputs.go | Detects label_command in on: and stages mergeable events for defaults generation. |
| pkg/workflow/compiler_orchestrator_workflow.go | Wires label-command extraction into compilation pipeline. |
| pkg/workflow/compiler_activation_job.go | Adds remove_trigger_label step + label_command output in activation job. |
| pkg/parser/schemas/main_workflow_schema.json | Adds on.label_command schema with name/names and events. |
| pkg/constants/constants.go | Adds step ID constant for remove_trigger_label. |
| actions/setup/js/remove_trigger_label.cjs | Implements label removal for issues/PRs (REST) and discussions (GraphQL). |
💡 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 err != nil { | ||
| return nil, fmt.Errorf("failed to marshal label-command names: %w", err) | ||
| } | ||
| steps = append(steps, fmt.Sprintf(" GH_AW_LABEL_NAMES: %q\n", string(labelNamesJSON))) |
| steps = append(steps, generateGitHubScriptWithRequire("remove_trigger_label.cjs")) | ||
|
|
||
| // Expose the matched label name as a job output for downstream jobs to consume | ||
| outputs["label_command"] = fmt.Sprintf("${{ steps.%s.outputs.label_name }}", constants.RemoveTriggerLabelStepID) |
| "type": "string", | ||
| }, | ||
| }, | ||
| } |
| if len(data.LabelCommandOtherEvents) > 0 { | ||
| for eventKey, eventVal := range data.LabelCommandOtherEvents { | ||
| if _, exists := labelEventsMap[eventKey]; exists { | ||
| // Event already present from label_command generation — keep ours | ||
| // (the condition handles filtering by label name at job level) | ||
| continue | ||
| } | ||
| labelEventsMap[eventKey] = eventVal | ||
| } |
pkg/workflow/tools.go
Outdated
| if err == nil { | ||
| yamlStr := strings.TrimSuffix(string(mergedEventsYAML), "\n") | ||
| yamlStr = parser.QuoteCronExpressions(yamlStr) | ||
| // Pass frontmatter so label names in "names:" fields get commented out | ||
| yamlStr = c.commentOutProcessedFieldsInOnSection(yamlStr, map[string]any{}) | ||
| data.On = yamlStr | ||
| } |
| // Detect label_command trigger | ||
| if _, hasLabelCommandKey := onMap["label_command"]; hasLabelCommandKey { | ||
| hasLabelCommand = true | ||
| // Set default label names from WorkflowData if already populated by extractLabelCommandConfig | ||
| if len(workflowData.LabelCommand) == 0 { | ||
| // extractLabelCommandConfig has not been called yet or returned nothing; | ||
| // set a placeholder so applyDefaults knows this is a label-command workflow. | ||
| // The actual label names will be extracted from the frontmatter in applyDefaults | ||
| // via extractLabelCommandConfig which was called in parseOnSectionRaw. | ||
| baseName := strings.TrimSuffix(filepath.Base(markdownPath), ".md") | ||
| workflowData.LabelCommand = []string{baseName} | ||
| } | ||
| // Clear the On field so applyDefaults will handle label-command trigger generation | ||
| workflowData.On = "" | ||
| } | ||
|
|
||
| // Extract other (non-conflicting) events excluding slash_command, command, label_command, reaction, status-comment, and stop-after | ||
| otherEvents = filterMapKeys(onMap, "slash_command", "command", "label_command", "reaction", "status-comment", "stop-after", "github-token", "github-app") | ||
| } |
| // TestLabelCommandNoClashWithExistingLabelTrigger verifies that label_command can coexist | ||
| // with a regular label trigger without creating a duplicate issues: YAML block. | ||
| func TestLabelCommandNoClashWithExistingLabelTrigger(t *testing.T) { | ||
| tempDir := t.TempDir() | ||
|
|
||
| // Workflow that has both a regular label trigger (schedule via default) and label_command | ||
| workflowContent := `--- | ||
| name: No Clash Test | ||
| on: | ||
| label_command: deploy | ||
| schedule: | ||
| - cron: "0 * * * *" | ||
| engine: copilot | ||
| --- | ||
|
|
||
| Both label-command and scheduled trigger. | ||
| ` | ||
|
|
… (smoke label) Co-authored-by: pelikhan <[email protected]>
Done in d8365f0.
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 update label ops doc page (use tech writer agent) |
|
@copilot merge main |
Add a new 'Label Command Trigger' section explaining: - One-shot command semantics (label auto-removed after trigger) - All frontmatter syntax variants (shorthand, map, events restriction, multiple names) - workflow_dispatch added for manual testing - Accessing matched label via needs.activation.outputs.label_command - Combining label_command with slash_command in the same workflow - NOTE callout about required write permission Update the 'Label Filtering' section to clarify it is for state-based routing where the label stays on the item. Add a comparison table between label_command and names: filtering to help users choose. Co-authored-by: Copilot <[email protected]>
Co-authored-by: pelikhan <[email protected]>
Done in 36f9f3a (label-ops.md) and 446975f (triggers.md).
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:
|
Co-authored-by: pelikhan <[email protected]>
Done in 2e28c38. Merged main, resolved the single conflict in 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 multi workflows may be removing the label so it is ok if the remove operation fails because the label is not present |
Done in 000bada. The catch block in |
|
@copilot review all code review comments |
The CI failure at run |
- Fix GH_AW_LABEL_NAMES env var: use single-quoted YAML string ('["deploy"]')
instead of %q-formatted string to pass raw JSON array to remove_trigger_label.cjs
- Add issues:write and discussions:write permissions to activation job when
label_command is configured so label removal calls succeed
- Set HasDispatchItemNumber=true when label_command injects workflow_dispatch,
enabling inputs.item_number fallbacks in expression mappings and concurrency keys
- Improve LabelCommandOtherEvents merge: union types arrays and preserve
non-types fields instead of silently dropping overlapping keys
- Return error from applyDefaults when yaml.Marshal fails for label-command events
- Add conflict validation in compiler_safe_outputs: reject issues/pull_request/
discussion triggers with non-label types alongside label_command
- Fix TestLabelCommandNoClashWithExistingLabelTrigger: use actual issues:labeled
trigger to test real key-clash merge; add TestLabelCommandConflictWithNonLabelTrigger
Co-authored-by: pelikhan <[email protected]>
Addressed all 7 code review comments in be2a502:
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 handle permissions so that discussions write is not needed if not enable for label commands only require minimal write permission. Use the on.github-app token if needed |
… when configured - Only add issues:write when issues/pull_request events are in label_command events - Only add discussions:write when discussion is in label_command events - When github-app is configured, skip GITHUB_TOKEN elevation and use app token for the remove_trigger_label step instead (with event-scoped app token scopes) - Include label_command in app token minting trigger condition - Compute FilterLabelCommandEvents once and reuse in both permission blocks Co-authored-by: pelikhan <[email protected]>
Done in 38cc6ab. Permissions are now event-scoped:
For example, When a 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:
|
Adds a new
label_commandtrigger that activates a workflow when a specific label is added to an issue, pull request, or discussion — and automatically removes that label on activation so it can be reapplied to re-trigger.Frontmatter
Compilation
issues,pull_request, anddiscussionevents withtypes: [labeled], filtered by the optionaleventsfieldworkflow_dispatchwith anitem_numberinput for manual testing; setsHasDispatchItemNumberso entity-number expressions gain|| inputs.item_numberfallbacks for manual dispatch runslabel_command, mergestypesarrays and preserves non-types fields (names, branches, etc.) rather than silently dropping overlapping keysslash_commandin the same workflow, label types (labeled) are merged into the existing slash_command event lists and the activation condition becomes(slash_command check) || (label name check)if:condition checksgithub.event.label.nameagainst the configured label namesissues,pull_request, ordiscussiontriggers with non-label types (e.g.opened) are combined withlabel_command; label-only types (labeled/unlabeled) are still allowed and mergedyaml.Marshalfailures during event-map generation now propagate as errors instead of silently producing an emptyon:blockActivation job
remove_trigger_labelstep that removes the label that triggered the run (REST API for issues/PRs, GraphQL for discussions; non-fatal on failure; 404 treated as info — label already removed by another concurrent run)issues: writeis only added whenissuesorpull_requestevents are enabled (PR label removal uses the issues REST API)discussions: writeis only added whendiscussionevents are enabledon.github-appis configured, GITHUB_TOKEN elevation is skipped entirely — theremove_trigger_labelstep uses the app token (minted with only the event-scoped permissions needed) insteadneeds.activation.outputs.label_commandwith the matched label name for use in downstream jobsSchema
label_commandis now a recognised property in theon:object, withname/namesandeventssub-fields validated against supported values (issues,pull_request,discussion).Example workflows
cloclo: Usesslash_command: cloclocombined withlabel_command: cloclo. Adding thecloclolabel to an issue, PR, or discussion now triggers the workflow (label is removed automatically) alongside the existing/clocloslash command.smoke-copilot: Useslabel_command: {name: smoke, events: [pull_request]}. Adding thesmokelabel to a pull request triggers the smoke test run and removes the label automatically.Documentation
docs/src/content/docs/patterns/label-ops.mdwith a new "Label Command Trigger" section covering syntax variants, label lifecycle (auto-removal), theneeds.activation.outputs.label_commandoutput, combining withslash_command:, and a comparison table vsnames:filtering.docs/src/content/docs/reference/triggers.mdwith a new "Label Command Trigger (label_command:)" subsection before "Label Filtering" with a concise syntax reference and link to the LabelOps patterns page.Original prompt
💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.