Triage and label new GitHub issues
Build a Kovca workflow that starts when a GitHub issue is opened, classifies it, adds a label, and on a bug comments asking for the steps to reproduce.
New GitHub issues land unlabeled, and the mix is bugs, feature requests, questions, and noise. Someone on the team sorts them by hand. A bug that never says how to reproduce it sits until a person asks, often days later.
This guide builds a workflow that starts from a GitHub issue, classifies it, adds one label for that category, and when the category is technical, comments on the issue asking for the steps, what was expected, and what happened. Anything that is not a newly opened issue is left alone. You'll start from a template, test it, and activate it on the repository.
The template already uses the GitHub Issue opened or changed trigger, Add labels to issue, and Comment on issue. It does not store a GitHub account. You choose the account when you activate. Until then the draft saves, and Studio tests simulate the GitHub steps.
If the repository only gets a handful of issues a week, GitHub labels and a line in the issue template asking for steps is less machinery than this. The workflow is for the repository where new issues sit unlabeled until someone has time.
What you'll build
Issue opened or changed, Is issue opened?, Issue text, Support Triage, Lookup, Add labels, Bug?, and Ask for steps.
| Step | Node | What it does |
|---|---|---|
| Input | GitHub: Issue opened or changed (github/issue_event) | Starts the workflow. The event fields are action, created_at, created_by, description, issue_id, number, title, and url. |
| Is issue opened? | Condition | true when action is opened. |
| Issue text | Text | The title and the description, which is what Support Triage reads. |
| Support Triage | Decision AI | Returns category, priority (low, medium, high), and requires_human_review. |
| Label | Lookup | Maps category to a list of one label name. |
| Add labels | GitHub Add labels to issue (action:github/add_labels@1) | Adds that list to the issue. Safe to retry. |
| Bug? | Condition | true when category is technical. |
| Ask for steps | Text | The comment asking for steps, expected, and actual. |
| Comment | GitHub Comment on issue (action:github/comment_on_issue@1) | Posts that text on the issue. Sent once. |
| Output | Output | Returns whether it was opened, the classification, the label, and whether the comment ran. |
The false branch of Is issue opened? goes straight to Output. A delivery whose action is not opened does not classify, label, or comment. The false branch of Bug? also goes straight to Output, after the label is on the issue. A question or a feature request is labeled and does not get the comment.
Support Triage does not return a bug, feature request, or how-to label of its own. technical is its bucket for errors, bugs, outages, crashes, slow performance, and integration problems. product covers both how a feature works and a feature request. Those two stay on one label unless you split them yourself later. priority is the severity this node returns. This template does not branch on it.
Build it in the Studio
1Start from the template
Click Use this template above, or open GitHub issue triage in Workflows.
The draft opens on the canvas. The components library is on the left. Triggers, AI, Action in an app, data transformation, and flow are the groups. GitHub is under Triggers and under Action in an app.
The library home. This draft starts on GitHub: Issue opened or changed.
Issue opened or changed is one GitHub trigger, next to the app's other triggers.
Create issue, Comment on issue, and Add labels to issue sit with the rest of GitHub's actions.
The draft is not callable until you publish and activate it. Three GitHub connections are still to choose.
2The workflow starts from an issue
Select the input step. It is GitHub: Issue opened or changed, not an API request. The account line is empty until you connect GitHub. Leave it empty while you test.
Repository shows acme / storefront. No GitHub account is selected.
The step is named Issue opened or changed. The description on the step says it is triggered when a new issue is added to the repository. Owner and Repo are required. The template sets them to acme and storefront. Those are stand-ins. The trigger's sample event is an issue on that repository (https://github.com/acme/storefront/issues/1287). Replace both with your owner and repository name.
Until an account is connected, those two settings show as one Repository field, with the stand-in acme / storefront, and the field stays closed. Connect GitHub and, when the account's repositories can be read, that field lists them. Picking one fills both. The same owner and repo have to be set again on Add labels and Comment, as fixed values on those steps, because those steps do not read the trigger settings. The event does not include owner or repo.
Event fields later steps read:
| Field | Used for |
|---|---|
input.action | Is issue opened? continues only when this is opened |
input.title | Issue text, and the comment |
input.description | Issue text |
input.number | Add labels and Comment, as the issue number |
The event also includes created_at, created_by, issue_id, and url. A manual run or an API call can omit fields. Is issue opened? needs action. Add labels and Comment need number.
A repeat of the same delivery does not start a second run. A later delivery for the same issue is a new run. Is issue opened? is what keeps that later run from labeling and commenting when its action is not opened.
3Only a newly opened issue continues
Select Is issue opened?. A condition checks one or more rules and continues on its true or false branch. This one has a single rule: input.action equals opened.
action equals opened. The true branch classifies the issue. The false branch goes to the result.
The trigger's sample event uses opened. The template follows that. Leave the rule as it is unless you have decided that some other action should be labeled too. The false branch does not call Support Triage, so that delivery uses no evaluation.
4Classify the issue
Issue text builds the message Support Triage reads. A GitHub issue has a title, and subject is optional on this node, so the template leaves it unmapped. A title can also be longer than the 200 characters subject allows, and the title field on the event allows 8,000.
{{input.title}}
{{input.description}}Select Support Triage. message is mapped from that text.
message comes from Issue text. subject is not mapped.
The node decides on fixed, published rules:
categoryisbilling,technical,account,product, orother.technicalis its bucket for errors, bugs, outages, crashes, slow performance, and integration problems.productcovers both how a feature works and a feature request.priorityislowfor a question or feedback,mediumwhen the customer has a problem but can keep working, andhighwhen they're blocked right now. A message the node treats as a security problem ishigh.requires_human_reviewistruewhen a legal threat or a security problem is reasonably possible, or when the category or priority itself is unclear.
message is required and accepts at most 8,000 characters. Issue text is the title, a blank line, and the description, and each of those event fields also allows 8,000, so the two together can be too long for this step. Shorten the template if you see that. If the title and the description are both empty, the text is blank and this step fails. A title with an empty description still classifies.
5Add a label for the category
Select Label. It is a Lookup. It reads nodes.triage.output.category. When the category equals a row's match, the result is that row's result. The result is a JSON array of label names, which is what Add labels asks for.
| Match | Result |
|---|---|
technical | ["bug"] |
product | ["product"] |
billing | ["billing"] |
account | ["account"] |
other | ["other"] |
| Fallback | ["triage"] |
technical maps to the JSON array bug. The fallback is the JSON array triage.
bug, product, billing, account, other, and triage are stand-ins. Change them to label names your repository already has. Keep each result a JSON array, like ["bug"]. Typing bug without the brackets stores the word, and Add labels expects a list.
Add labels is GitHub's Add labels to issue action. It is on the path after Label, and before Bug?, so every opened issue is labeled, including the ones that are not bugs.
| Add labels field | Value |
|---|---|
Owner (owner) | acme |
Repo (repo) | storefront |
Issue Number (issue_number) | input.number |
Labels (labels) | nodes.queue.output.result |
acme and storefront are the same stand-ins as the trigger. Replace them with your owner and repository name. Issue Number is the event's number, an integer. Labels accepts at most 100 names.
Owner and Repo are the fixed values acme and storefront. Issue Number is input.number. Labels comes from Label. No GitHub account is selected.
If it fails is Stop the run. If the label can't be added, the run fails and the comment does not run. Add labels is safe to retry: Kovca retries it if GitHub is briefly unavailable. Choose the GitHub account on this step before you activate. Studio tests simulate it and do not change the issue.
6Ask for steps when it is a bug
Select Bug?. One rule: nodes.triage.output.category equals technical.
category equals technical. The true branch posts the comment. The false branch goes to the result.
The true branch is every issue Support Triage calls technical, including one that already lists steps, and including one where requires_human_review is true. The template does not read the description for the words "steps" or "expected". If you only want the comment in some of those cases, change the rule.
Ask for steps is the comment.
Thanks for opening #{{input.number}} ({{input.title}}). To look into this we still need the steps you took, what you expected to happen, and what happened instead. Reply on this issue with those three.Comment is GitHub's Comment on issue action.
| Comment field | Value |
|---|---|
Owner (owner) | acme |
Repo (repo) | storefront |
Issue Number (issue_number) | input.number |
Body (body) | nodes.ask.output.text |
The body is GitHub Flavored Markdown, at most 8,000 characters. This template's text is well under that. Replace acme and storefront here too. The step can also comment on a pull request number, because that is what the action does. This workflow passes the issue number from Issue opened or changed.
Owner and Repo are acme and storefront. Issue Number is input.number. Body comes from Ask for steps. If it fails, the run continues.
If it fails is Continue (output.ok is false). If the comment does not post, the label is already on the issue and the run still succeeds, with commented false. Comment on issue is sent once: Kovca does not retry it on its own. Choose the same GitHub account. Studio tests simulate it and do not post.
7Test it
Open the Test tab. The template's examples are listed above the input. Leave Send real requests off: Add labels and Comment return their samples. Nothing is posted to GitHub.
Run Cart total when you want an issue about a missing shipping fee, and How-to question when you want a shorter one. What Support Triage returns depends on the issue. A technical category takes Comment. Any other category takes Output after Add labels, and Ask for steps and Comment are skipped. An input whose action is not opened skips the classification too.
Before you activate, run a stack of real issues from the repository, including a few bugs and a few that should only have been labeled. When the routes look right, turn on Send real requests once, with the GitHub account chosen, to check those calls.
Publish, call it, and activate it
The same workflow can be called over the API. The body uses the trigger's field names. Create an API key under API keys and keep it on your backend.
curl https://kovca.io/api/v1/workflows/wf_.../execute \
-H "Authorization: Bearer $KOVCA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3092845511" \
-d '{
"input": {
"action": "opened",
"created_at": "2026-11-02T14:03:00Z",
"created_by": "maria-chen",
"description": "Since the 1.42 release, the cart total does not include the shipping fee for orders to Canada.",
"issue_id": 3092845511,
"number": 1287,
"title": "Cart total missing shipping for CA orders",
"url": "https://github.com/acme/storefront/issues/1287"
}
}'A run that classifies that issue as technical takes the comment path. The fields below are that shape, with priority medium. Add labels and Comment return their samples, so labeled and commented are true. The comment is Ask for steps filled in from that input. What the node returns for a given issue depends on the text, so run your own examples before you treat a route as settled.
{
"request_id": "req_...",
"run_id": "run_...",
"workflow": { "id": "wf_...", "version": 1 },
"status": "succeeded",
"output": {
"opened": true,
"category": "technical",
"priority": "medium",
"requires_human_review": false,
"labels": ["bug"],
"labeled": true,
"comment": "Thanks for opening #1287 (Cart total missing shipping for CA orders). To look into this we still need the steps you took, what you expected to happen, and what happened instead. Reply on this issue with those three.",
"commented": true
},
"steps": [
{ "id": "input", "status": "succeeded" },
{ "id": "opened", "status": "succeeded" },
{ "id": "body", "status": "succeeded" },
{ "id": "triage", "status": "succeeded" },
{ "id": "queue", "status": "succeeded" },
{ "id": "apply", "status": "succeeded" },
{ "id": "bug", "status": "succeeded" },
{ "id": "ask", "status": "succeeded" },
{ "id": "comment", "status": "succeeded" },
{ "id": "output", "status": "succeeded" }
],
"evaluations": 1
}Use the issue id as the Idempotency-Key. A retry returns the original run instead of labeling and commenting again. Kovca does not store the issue or the output, so keep the response from the first call. Values from steps that didn't run come back as null. On an issue Support Triage does not call technical, comment and commented are null. On a delivery whose action is not opened, opened is false and the classification, the label, and the comment are null.
To run it from the repository, open Activate. The checklist is what's left: connect GitHub, choose that account on the trigger, on Add labels, and on Comment, and replace acme and storefront in all three places. Publish & activate publishes the draft and starts it. The checklist's activate row runs on every issue opened or changed. A live automation runs that pinned version. Later edits don't reach it until you choose Publish & update.
Connect GitHub is still to do. The panel shows up to 19 credits per event.
You sign in on GitHub's page. The sign-in page names Composio, which holds the account access and uses it only for GitHub. Kovca keeps an ID for the account. Don't connect an account until the routes look right in the Test tab.
A new issue on the repository starts a run. Follow runs in Executions. While an automation is paused, events that arrive are skipped. A failed run is not retried on its own, because the label or the comment may already have happened.
If the repo is quiet
You don't need this if a person can read new issues, and the list is short. GitHub can notify you, and the issue template can ask for steps before the issue is opened. That's a fair setup for a few issues a week.
What the workflow adds is the same classification on every opened issue, one label from that category, and a comment asking for steps when the category is technical. You still have to replace the stand-in repository, choose the GitHub account, and read a sample of results before you activate it.
Make it yours
- Skip the comment when a person should review it. Add
nodes.triage.output.requires_human_reviewis false on Bug?, and leave Match on all. A technical issue that needs a person is labeled and not commented on. - Use your label names. Label's results are JSON arrays.
["bug"]is a stand-in. - Ask on another category too. Bug? only matches
technical.productis both a how-to and a feature request, so it stays on theproductlabel until you add a case of your own. - Roll it out on a copy of the repository. Point Owner and Repo at a test repository for a week and compare the labels with what the team would have applied.
- Change it without surprising a live repository. Edit the draft and use Publish & update. The running version stays pinned until you do.
What it costs
Credits, and only for steps that succeed. Studio tests are free, and simulated actions are free.
- The run: 10 credits when a GitHub event starts it, 1 credit when the API starts it.
- Support Triage: 5 credits, and only when
actionisopened. - Add labels: 2 credits when it runs for real.
- Comment on issue: 2 credits when it runs for real, and only on the technical branch.
The conditions, the text steps, and the lookup are free. A branch that doesn't run isn't charged. The Activate panel adds every step together, including steps on branches that can't run in the same event, so its number is an upper bound. With this template that upper bound is 19 credits per event. The response's credits field is the run's total. See billing, the workflows API, HTTP actions, and automations.