> ## Documentation Index
> Fetch the complete documentation index at: https://codspeed.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Macro Runners on GitHub Actions

> Run the benchmark jobs of your GitHub Actions workflows on CodSpeed macro runners

export const CIWorkflow = ({minimal = false, enableWorkflowDispatch = true, runsOn = "ubuntu-latest", highlight = [], mode, modes, submodules = false, preSteps = [], buildSteps = ["# ...", "# Setup your environment here:", "#  - Configure your Python/Rust/Node version", "#  - Install your dependencies", "#  - Build your benchmarks (if using a compiled language)", "# ..."], benchmarkCommand = ["<Insert your benchmark command here>"], jobName = "Run benchmarks", env = {}}) => {
  const modeList = modes || (mode ? [mode] : undefined);
  if (!modeList || modeList.length === 0) {
    throw new Error("mode or modes is required");
  }
  const indent = (lines, depth) => {
    const reindentedLines = lines.map(l => l.length === 0 ? l : (" ").repeat(depth) + l);
    return reindentedLines.join("\n");
  };
  const workflowDispatchSection = enableWorkflowDispatch ? "  # `workflow_dispatch` allows CodSpeed to trigger backtest\n" + "  # performance analysis in order to generate initial data.\n" + "  workflow_dispatch:\n" : "";
  let yaml = "";
  if (!minimal) {
    yaml += `
name: CodSpeed Benchmarks

on:
  push:
    branches:
      - "main" # or "master"
  pull_request:
`;
    yaml += workflowDispatchSection;
  }
  yaml += `
jobs:
  benchmarks:
    name: ${jobName}
    runs-on: ${runsOn}`;
  if (!minimal) {
    yaml += `
    permissions: # optional for public repositories
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed`;
  }
  if (preSteps.length > 0) yaml += "\n" + indent(preSteps, 4);
  yaml += `
    steps:
      - uses: actions/checkout@v5`;
  if (submodules) {
    const value = typeof submodules === "string" ? submodules : "true";
    yaml += `\n        with:\n          submodules: ${value}`;
  }
  yaml += "\n" + indent(buildSteps, 6);
  const modeValue = modeList.join(",");
  yaml += `
      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v5
        with:
          mode: ${modeValue}`;
  if (benchmarkCommand.length > 0) {
    const indentedBenchCommand = benchmarkCommand.length > 1 ? benchmarkCommand[0] + "\n" + indent(benchmarkCommand.slice(1), 12) : benchmarkCommand;
    const runLine = indent(["run: "], 10) + indentedBenchCommand;
    yaml += `\n${runLine}`;
  }
  const envEntries = Object.entries(env);
  if (envEntries.length > 0) {
    const envLines = ["env:", ...envEntries.map(([k, v]) => `  ${k}: ${v}`)];
    yaml += "\n" + indent(envLines, 8);
  }
  return <CodeBlock language="yaml" highlight={JSON.stringify(highlight)} {...minimal || ({
    filename: ".github/workflows/codspeed.yml",
    icon: "github"
  })}>
      {yaml}
    </CodeBlock>;
};

[Macro runners](/docs/features/macro-runners) are bare-metal machines managed by
CodSpeed, for [walltime](/docs/instruments/walltime) measurements with low variance.
A GitHub-hosted runner shares its host with other tenants, so the walltime of a
benchmark moves from one run to the next for reasons unrelated to your code. A
macro runner is dedicated to your job for its whole duration.

On GitHub Actions, CodSpeed registers a macro runner in your organization for
each queued benchmark job and removes it once the job is done. Your side of it
is the `runs-on` label of the benchmark job.

## Prerequisites

* `CodSpeedHQ/action >= 3.1.0`.
* A GitHub organization. Registering a self-hosted runner on a repository rather
  than on an organization needs the **Repository: Administration** permission,
  which is too broad, so macro runners are not available on personal accounts.
  Transfer the repository to an organization to use them.
* CodSpeed enabled on the repository.

## 1. Point the benchmark job at a macro runner

Replace `runs-on: ubuntu-latest` with the label of the
[runner](/docs/features/macro-runners#runners) you want, and set the `mode` of the
action to `walltime`:

<Tabs>
  <Tab title="AMD Ryzen 9950X">
    <CIWorkflow minimal runsOn="codspeed-macro-x86-ryzen-9950x-ubuntu-24-04" mode="walltime" highlight={[4, 16]} />
  </Tab>

  <Tab title="AWS Graviton">
    <CIWorkflow minimal runsOn="codspeed-macro" mode="walltime" highlight={[4, 16]} />
  </Tab>
</Tabs>

Everything else about the workflow is unchanged, see
[the GitHub Actions guide](/docs/integrations/ci/github-actions#1-create-the-benchmarks-workflow).

<Tip>
  **Caching**

  A macro runner may not share the architecture of the GitHub-hosted runners your
  other jobs use, so include `${{ runner.arch }}` in cache keys, or a job restores
  artifacts built for another architecture:

  ```yaml {4} theme={null}
  - uses: actions/cache@v4
    with:
      path: # insert your cache path here
      key: pip-${{ hashFiles('pyproject.toml') }} # [!code --]
      key: pip-${{ runner.arch }}-${{ hashFiles('pyproject.toml') }} # [!code ++]
  ```
</Tip>

## 2. Open a pull request

Open a pull request from the branch carrying the change: the benchmark job runs
on a macro runner and the pull request receives its performance report.

The walltime measurements of a run are comparable to the baseline recorded on
your default branch only if both ran on macro runners, so merge the change to
record a first baseline on a macro runner.

## Repository access

### Private repositories

Macro runners work with the private repositories of your organization without
further setup.

### Public repositories

GitHub keeps self-hosted runners away from public repositories by default, since
a pull request from a fork could run code on them. To use macro runners on a
public repository, go to **Organization Settings** → **Actions** → **Runner
groups** → **Default** in GitHub and allow public repositories:

<Frame>
  <img src="https://mintcdn.com/codspeed/_9gjNuBHMdsdxHot/instruments/walltime/assets/github-public-repo-access.png?fit=max&auto=format&n=_9gjNuBHMdsdxHot&q=85&s=fd62b02d6c9d620f4fa8723334d568c3" className="rounded-xl w-full max-w-lg mx-auto" alt="Enabling macro runners for public repositories" width="1400" height="372" data-path="instruments/walltime/assets/github-public-repo-access.png" />
</Frame>

## Next Steps

<CardGroup cols={2}>
  <Card title="Macro Runners" icon="server" href="/docs/features/macro-runners">
    The runners, and what they are for
  </Card>

  <Card title="Walltime Instrument" icon="stopwatch" href="/docs/instruments/walltime">
    Measure and interpret walltime benchmarks
  </Card>

  <Card title="Configure GitHub Actions" icon="cog" href="/docs/integrations/ci/github-actions/configuration">
    Authentication, parallel jobs and advanced options
  </Card>

  <Card title="Performance Checks" icon="shield-check" href="/docs/features/performance-checks">
    Catch regressions on every pull request
  </Card>
</CardGroup>
