feat: add label-command trigger (On Label Command)#21118
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…fy owner/repo extraction Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
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. | ||
| ` | ||
|
|
There was a problem hiding this comment.
Agreed — the smoke test agent noticed this too during review. The test name implies it covers a clash scenario, but the workflow content only has schedule alongside label_command, not an actual issues/pull_request/discussion with types: [labeled]. A real clash test would verify the YAML merge logic.
📰 BREAKING: Report filed by Smoke Copilot
… (smoke label) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
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 <4175913+pelikhan@users.noreply.github.com>
… 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 <4175913+pelikhan@users.noreply.github.com>
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:
|
|
✅ All tools validated successfully! Agent Container Smoke Test confirms agent container is ready. |
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟 |
|
🎬 THE END — Smoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨ |
|
📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing... |
Agent Container Tool Check
Result: 12/12 tools available ✅ Overall Status: PASS
|
|
Smoke Test: Codex Engine Validation
Warning
|
There was a problem hiding this comment.
Smoke test review of PR #21118 (label-command trigger). The remove_trigger_label.cjs implementation looks solid overall — good handling of workflow_dispatch, discussion GraphQL path, and 404 tolerance. Two minor observations: (1) label validation before removal, (2) let → const for labelNames.
📰 BREAKING: Report filed by Smoke Copilot
| try { | ||
| labelNames = JSON.parse(labelNamesJSON); | ||
| if (!Array.isArray(labelNames)) { | ||
| core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_LABEL_NAMES must be a JSON array.`); |
There was a problem hiding this comment.
The GH_AW_LABEL_NAMES env var is parsed but only the context.payload?.label?.name is used for removal. If the labeled event fires for a label that is NOT in labelNames, the step still removes it and sets label_name to the triggering label. Consider validating that triggerLabel is included in labelNames before removing, to avoid accidentally removing unrelated labels.
| core.setFailed(`${ERR_CONFIG}: Configuration error: Failed to parse GH_AW_LABEL_NAMES: ${getErrorMessage(error)}`); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
Minor nit: labelNames is declared with let but never reassigned after the JSON.parse. Consider using const here for clarity.
|
Commit pushed:
|
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
|
Smoke Test Results — Run §23126604129 Core: ✅ GitHub MCP ✅ mcpscripts-gh ✅ Serena ✅ Make Build ✅ Playwright ✅ Tavily ✅ File Write ✅ Bash ✅ Discussion ✅ AW Status PR Review: ✅ Update PR ✅ Review Comments ✅ Submit Review Overall: PARTIAL (2 skipped, 0 failed)
|
There was a problem hiding this comment.
💥 Automated smoke test review - all systems nominal!
💥 [THE END] — Illustrated by Smoke Claude
| @@ -2,9 +2,7 @@ | |||
| on: | |||
| slash_command: | |||
| name: cloclo | |||
There was a problem hiding this comment.
Clean simplification — replacing the explicit issues: types: [labeled] + names: block with a single label_command: cloclo line is much more readable. The intent is immediately clear.
| core.info(`Event ${eventName} has no label payload – skipping label removal.`); | ||
| core.setOutput("label_name", ""); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Good defensive check here — verifying triggerLabel exists before proceeding prevents cryptic downstream failures. The early return with empty output is the right pattern for non-fatal skips.
…igger Add two new glossary entries based on recent changes: - Ecosystem Identifiers: Named shorthand references to predefined domain sets (python, node, go, github, dev-tools, local, default-safe-outputs) used in network.allowed and safe-outputs.allowed-domains. Includes the new default-safe-outputs compound ecosystem identifier (renamed from default-redaction in #21114). - Label Command Trigger (label_command): New trigger type from #21118 that activates when a specific label is added to an issue, PR, or discussion. The label is automatically removed on activation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
✨ PR Review Safe Output Test - Run 23126604129