Skip to content
Latchkey

Auto-label workflow (apache/burr)

The Auto-label workflow from apache/burr, explained and optimized by Latchkey.

A

CI health: A - excellent

Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: apache/burr.github/workflows/auto-label.ymlLicense Apache-2.0View source

What it does

This is the Auto-label workflow from the apache/burr repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
#<!--
#     Licensed to the Apache Software Foundation (ASF) under one
#     or more contributor license agreements.  See the NOTICE file
#     distributed with this work for additional information
#     regarding copyright ownership.  The ASF licenses this file
#     to you under the Apache License, Version 2.0 (the
#     "License"); you may not use this file except in compliance
#     with the License.  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing,
#     software distributed under the License is distributed on an
#     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#     KIND, either express or implied.  See the License for the
#     specific language governing permissions and limitations
#     under the License.
#-->
# Auto-labeling for new issues and PRs
# Part of #690 (repo governance) and #696 (auto-labeling)
#
# - New issues get status/needs-triage
# - PRs get area/* labels based on changed files
# - PRs get pr/needs-rebase when they have merge conflicts
#
# Security: this workflow only uses numeric IDs from context (issue_number,
# pull_request.number). No untrusted string input is interpolated.

name: Auto-label

on:
  issues:
    types: [opened]
  pull_request_target:
    types: [opened, synchronize]

permissions:
  contents: read
  issues: write
  pull-requests: write

jobs:
  triage-issues:
    if: github.event_name == 'issues'
    runs-on: ubuntu-latest
    steps:
      - name: Add status/needs-triage to new issues
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              labels: ['status/needs-triage']
            });

  label-prs:
    if: github.event_name == 'pull_request_target'
    runs-on: ubuntu-latest
    steps:
      - name: Apply area/* labels based on changed files
        uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          configuration-path: .github/labeler.yml
          sync-labels: false

  check-mergeable:
    if: github.event_name == 'pull_request_target'
    runs-on: ubuntu-latest
    steps:
      - name: Check for merge conflicts
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const LABEL = 'pr/needs-rebase';

            // Wait for GitHub to compute mergeability
            await new Promise(r => setTimeout(r, 5000));

            const { data: pr } = await github.rest.pulls.get({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.payload.pull_request.number,
            });

            const labels = pr.labels.map(l => l.name);
            const hasLabel = labels.includes(LABEL);

            if (pr.mergeable === false && !hasLabel) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [LABEL],
              });
              console.log(`#${pr.number}: added ${LABEL}`);
            } else if (pr.mergeable === true && hasLabel) {
              await github.rest.issues.removeLabel({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                name: LABEL,
              });
              console.log(`#${pr.number}: removed ${LABEL}`);
            } else {
              console.log(`#${pr.number}: no change (mergeable=${pr.mergeable}, hasLabel=${hasLabel})`);
            }

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

#<!--
#     Licensed to the Apache Software Foundation (ASF) under one
#     or more contributor license agreements.  See the NOTICE file
#     distributed with this work for additional information
#     regarding copyright ownership.  The ASF licenses this file
#     to you under the Apache License, Version 2.0 (the
#     "License"); you may not use this file except in compliance
#     with the License.  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing,
#     software distributed under the License is distributed on an
#     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#     KIND, either express or implied.  See the License for the
#     specific language governing permissions and limitations
#     under the License.
#-->
# Auto-labeling for new issues and PRs
# Part of #690 (repo governance) and #696 (auto-labeling)
#
# - New issues get status/needs-triage
# - PRs get area/* labels based on changed files
# - PRs get pr/needs-rebase when they have merge conflicts
#
# Security: this workflow only uses numeric IDs from context (issue_number,
# pull_request.number). No untrusted string input is interpolated.
 
name: Auto-label
 
on:
  issues:
    types: [opened]
  pull_request_target:
    types: [opened, synchronize]
 
permissions:
  contents: read
  issues: write
  pull-requests: write
 
jobs:
  triage-issues:
    timeout-minutes: 30
    if: github.event_name == 'issues'
    runs-on: latchkey-small
    steps:
      - name: Add status/needs-triage to new issues
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              labels: ['status/needs-triage']
            });
 
  label-prs:
    timeout-minutes: 30
    if: github.event_name == 'pull_request_target'
    runs-on: latchkey-small
    steps:
      - name: Apply area/* labels based on changed files
        uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          configuration-path: .github/labeler.yml
          sync-labels: false
 
  check-mergeable:
    timeout-minutes: 30
    if: github.event_name == 'pull_request_target'
    runs-on: latchkey-small
    steps:
      - name: Check for merge conflicts
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const LABEL = 'pr/needs-rebase';
 
            // Wait for GitHub to compute mergeability
            await new Promise(r => setTimeout(r, 5000));
 
            const { data: pr } = await github.rest.pulls.get({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.payload.pull_request.number,
            });
 
            const labels = pr.labels.map(l => l.name);
            const hasLabel = labels.includes(LABEL);
 
            if (pr.mergeable === false && !hasLabel) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [LABEL],
              });
              console.log(`#${pr.number}: added ${LABEL}`);
            } else if (pr.mergeable === true && hasLabel) {
              await github.rest.issues.removeLabel({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                name: LABEL,
              });
              console.log(`#${pr.number}: removed ${LABEL}`);
            } else {
              console.log(`#${pr.number}: no change (mergeable=${pr.mergeable}, hasLabel=${hasLabel})`);
            }
 

What changed

This workflow runs 3 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow