# Runners and jobs

> Install self-hosted runners on your machines, see which ones are busy, and find out why a job isn’t starting.

Muxen installs GitHub Actions runners on your machines and shows them next to everything else running there. It downloads the runner, registers it with GitHub using your `gh` login, and starts it. You never copy a registration token.

## Watch an organization or a repository

Runners register with an organization or a repository. Tell Muxen which ones you use in **Settings › GitHub › Watching**. It lists your organizations, so watching one is a click. Or from a terminal:

```
muxen watch acme      # an organization
muxen watch acme/api  # one repository
muxen unwatch acme
```

For what it watches, Muxen reads each runner’s live status from GitHub, and the queued and running jobs of the most recently pushed repositories. It asks every 20 seconds or so; unchanged answers don’t count against your rate limit.

> **Warning:** An organization’s runners need the `admin:org` permission on your GitHub login. **Settings › GitHub** says when it’s missing and has a **Grant access** button. Or run `gh auth refresh -h github.com -s admin:org`. A repository’s runners need admin access to that repository.

## Install runners

On a machine’s page, click **Install runner**. Pick:

- **Register with**: one of the organizations or repositories you watch.
- **How many**: up to 16 at once, and their **Names** if you want your own.
- **Extra labels**, such as `gpu`. GitHub adds `self-hosted`, the OS and the architecture by itself.
- **Run as**: how each runner stays running (below).

Muxen picks the right runner build for the machine, downloads it, registers it and starts it. The progress shows in Activity (`⌘J`).

### Run as

| Choice | What it does |
|---|---|
| **Auto** | The best fit for the machine: a service where it can, else Docker, else a background process. The default. |
| **Service** | Starts at boot: launchd on macOS, a Windows service, systemd on Linux. On Linux this needs passwordless sudo. |
| **Docker** | A container per runner that restarts with Docker. No sudo. Jobs are kept apart from the machine. |
| **Background** | No root needed, but it stops when the machine restarts. |

Docker runners can’t use the machine’s Docker unless you share its socket (`--docker-socket` from the CLI). That’s root on the machine for every workflow that runs there, so only do it for workflows you trust.

## Runner states

Each runner is **working** (running a job), **idle**, **offline**, or **unknown**. Where the state comes from matters:

- **GitHub**: live, for runners in what you watch.
- **The last machine check**: whether the runner’s process or service is running, for runners GitHub doesn’t tell Muxen about.

Right-click a runner to **Stop**, **Start** or **Restart** it, **Show logs** (its newest diagnostic log), open the job it’s running on GitHub, or **Remove runner…**, which deregisters it from GitHub. A runner that GitHub knows but no machine lists shows up on its own, so you can find and remove leftovers.

## Jobs

The Jobs page (`⌃2`) shows self-hosted jobs that are **Queued** and **Running**, with how many runners are busy.

![The Jobs page: queued jobs, some with no runner that fits, and running jobs with the runner each is on.](https://muxin.dev/images/shots/jobs-dark.png?v=bb041b1c)

*A queued job no runner can take is flagged.*

Right-click a job to show its runner or **Cancel run…**. GitHub cancels whole workflow runs, not single jobs, so that stops every job in the run.

## When a job doesn’t start

A job waits for an online runner that has *all* of its labels. When none has, the Jobs page says how many jobs have “no runner that fits”. Usually the fix is one of these:

1. A runner with the labels is offline. Start it, or check its machine: an offline machine explains every runner on it.
2. No runner has one of the labels, say `gpu`. Install one with that label.
3. The runner is registered to a different organization or repository than the workflow.

For a runner that won’t stay online, **Show logs** first. See [Troubleshooting](https://muxin.dev/docs/troubleshooting) for unreachable machines.

## From the command line

```
muxen runners [--machine beacon] [--state offline]
muxen jobs
muxen install beacon --count 2 --label gpu
muxen install beacon --scope acme/api --mode docker
muxen runner restart beacon/beacon-01
muxen runner logs beacon/beacon-01
muxen runner remove beacon/beacon-01 [--delete-files]
muxen cancel-run 123456789
```

Runner ids are `<machine>/<runner>`, such as `beacon/beacon-01`. A runner GitHub knows but no machine lists is `github/<name>`. Runners also live in `config.toml` under their machine, so Muxen knows what each machine should host.

For GitHub Enterprise Server, point Muxen at your server’s API: `muxen settings --api-url https://HOST/api/v3`.
