# Troubleshooting

Fix setup problems, unavailable machines, queued jobs, and unexpected fallback runs.

## My repository is missing or unavailable

1. Select the repository’s GitHub account in Mirage’s account menu.
2. In Settings, use **Manage access on GitHub** and confirm the repository is selected for
   the Mirage App.
3. Return to Mirage and select **Sync repositories** or **Sync repository details**.
4. Confirm it is a private, unarchived repository. Public repositories cannot use Mirage
   execution.
5. Use **Retry provisioning** if setup reports an error. Private repositories selected in
   GitHub do not need a separate Mirage switch.

If you cannot change these settings, ask an owner of the GitHub organization. Mirage roles
follow GitHub: only organization owners manage settings.

## My Mac is connected but not ready

Open the Mac in Machines and check the individual runner profile. A connection means Mirage
can reach the host; it does not mean every runner environment is ready.

| What to check | What to do |
| --- | --- |
| Image download or preparation | Wait for preparation and its self-test. If it fails, read the profile’s error and Next step. |
| Insufficient disk or memory | Free space or reduce the configured runner resources. Image preparation needs room beyond the job’s writable disk budget. |
| Paused, on battery, or under load | Resume the host, connect power, and allow resource pressure to settle. |
| Linux x64 unavailable | Check that Rosetta is installed and the x64 self-test passes. |
| macOS unavailable | Check that the profile is enabled and its image is prepared. Older GUI-bridge installations need the current signed host package; pause and drain before rerunning bootstrap. |
| Offline after reboot | Check the network and power. FileVault may need a person to unlock the disk; native macOS jobs also need a desktop login. |

Use `mirage host doctor` on the Mac for the detailed checks. See [Use the CLI](https://mirageci.com/docs/cli.md) if
the command is not found.

## My job stays queued

GitHub shows a waiting job as queued without a reason. Mirage combines its reports in one
**Mirage** check per commit. In the repository's **GitHub check** setting, choose Always,
After 5 minutes, After 15 minutes (the default), Custom, or Never. The delay controls the
first waiting report; infrastructure failures appear immediately unless reporting is Never.

The check explains why jobs haven't started, what to do, and what Mirage does when the
repository's [queue timeout](https://mirageci.com/docs/workflow-routing.md#queue-timeout) ends. Workflows, matrix
jobs and reruns update the same check. Waiting completes as neutral once no job is waiting,
even if jobs are now running. Source infrastructure failures report failure; superseded
attempts stay in history. Its details link opens Runs in Mirage.

If the check doesn’t explain the wait:

1. Confirm GitHub App access and repository provisioning finished.
2. Compare the workflow’s `runs-on` label with the [supported runner
   labels](https://mirageci.com/docs/workflow-routing.md#runner-profiles).
3. Check Machines for a machine available to the selected account. Confirm the required
   profile is Ready and has headroom for the job’s allocation.
4. Check the machine’s **Available to** list and any capacity grant needed by the account.
5. Check GitHub for an Actions outage and confirm Mirage’s dashboard is reachable.

If you need to unblock CI while Mirage is unavailable, restore the job’s original
GitHub-hosted label and push the workflow. For a workflow already using the attempt
expression, cancelling and manually rerunning it in GitHub selects the hosted label on the
next attempt. Check for side effects before rerunning a workflow.

## My job ran on GitHub instead of my Mac

Open the job in Mirage and inspect its route reason and attempt number. Common causes are:

- No machine took the job within the repository’s [queue
  timeout](https://mirageci.com/docs/workflow-routing.md#queue-timeout): the Mac was busy, paused, asleep, on
  battery, or short of resources. Check or change the timeout on the repository’s page in
  [Repos](https://mirageci.com/repos).
- The required runner profile was not ready or startup failed.
- The global emergency switch was active, the repository was disconnected or ineligible,
  or the event did not meet Mirage's trust checks.
- The workflow was manually rerun and its attempt expression selected GitHub.

Fix the reported cause and trigger a new workflow run to try local execution again.
Re-running an existing run keeps its later attempt number and will still select the hosted
fallback with the documented expression.

## My job failed after waiting for a machine

A job on a literal `mirage-*` label has no fallback. When no machine takes it within the
repository’s [queue timeout](https://mirageci.com/docs/workflow-routing.md#queue-timeout), Mirage adds a failed
**Mirage** check that says how long it waited, then cancels the run. Open the check’s
details for the explanation, next steps, and links to the workflow and machines. To fix
it:

- Make a machine available. In [Machines](https://mirageci.com/machines), check for one whose profile is Ready,
  that isn’t paused, and that lists the account under **Available to**.
- If jobs often wait, add a machine, increase its background work budget, or request smaller runners.
- If long waits are expected, raise the timeout on the repository’s page in [Repos](https://mirageci.com/repos).
- If the job should run on GitHub instead, use the [attempt
  expression](https://mirageci.com/docs/workflow-routing.md#the-contract) so it falls back.

Rerun the workflow once a machine can take the job.

## Fallback did not run

Check for a literal `runs-on: mirage-*` label. A literal label targets Mirage on every
attempt. For hosted fallback, use the full expression:

Linux x64 with hosted fallback:

```yaml
runs-on: ${{ github.run_attempt == '1' && 'mirage-linux-x64' || 'ubuntu-24.04' }}
```

Automatic fallback also requires the GitHub App to retain access and Mirage to be reachable
to request the rerun. Direct Cloudflare jobs do not have automatic fallback. A normal failed
test does not trigger fallback either; inspect its logs in GitHub.

If attempt 2 does not start on GitHub’s runner, check whether a spending limit or budget is
blocking GitHub-hosted runners. See [When GitHub-hosted runners are
blocked](https://mirageci.com/docs/workflow-routing.md#hosted-runners-blocked).

## My job ran out of disk

A step that fails with `No space left on device` usually means the runner’s disk filled up,
not that your code is wrong. When a Linux job on a Mac fails with its disk at least 97% full,
Mirage says so: [Runs](https://mirageci.com/runs) shows **Out of disk** beside the job, and a failed
**Mirage** check on the commit says how much of the disk the job used and where it
failed.

Each Linux job on a Mac gets its own disk, the size of the job’s disk allocation
(6 GiB unless you change it). The runner image and a swap file sit on that disk too, so your
steps have less than the budget. The job’s Disk panel in Runs shows its peak use against the disk.

To fix it:

- Raise the job’s disk allocation under **Job routes** in [Repos](https://mirageci.com/repos). The Mac needs
  that much free space for each job it runs at once, within its background work budget.
- Or have the job write less: delete build outputs and caches it no longer needs, and prune
  Docker images it pulled.
- For a Cloudflare job, choose a larger [runner size](https://mirageci.com/docs/cloudflare.md#labels).

To rerun these jobs elsewhere instead, open the repository in [Repos](https://mirageci.com/repos) and set **When
a job runs out of disk** to **Fall back**. Mirage then reruns the job once on its fallback
runner rather than reporting it. A job a setup pull request routes reruns on its original
runner; a job using the [attempt expression](https://mirageci.com/docs/workflow-routing.md#fallback) reruns its
whole workflow, whose next attempt runs on GitHub. A job with a literal `mirage-*` label has
no fallback, so Mirage reports it either way, as it does when the attempt that ran out of
disk already was the fallback. An ordinary test failure never falls back.

Mirage recognizes a full disk only on a Mac’s Linux jobs, where each job’s disk is its own
and its size is the budget. Jobs on a Linux host’s Docker runtime, on Proxmox, in macOS VMs
and on Cloudflare fail as usual; read the failed step’s log in GitHub.

## Cloudflare or a provider comparison is unavailable

For Cloudflare, check that the repository’s Cloudflare switch is on and the selected Shared
or BYO backend is ready. Shared mode needs access enabled by Mirage; BYO setup failures show
an error in Settings. Docker-dependent jobs need a different provider.

For comparisons, confirm the onboarding PR is merged and the job’s configuration is applied.
Refresh job discovery if the workflow changed, approve any requested GitHub App permissions,
and read the provider’s unavailable reason. Only eligible completed jobs can start an
experiment. See [Compare CI providers](https://mirageci.com/docs/ci-providers.md) for the supported workflow
shapes.

## Collect diagnostics

Record the GitHub run and job links, repository and account, attempt number, runner label,
and Mirage’s route reason. For a host problem, include the machine’s Mirage version and the
affected profile’s readiness message.

On the affected Mac · requires the Mirage CLI:

```bash
mirage host status
mirage host doctor
mirage host diagnostics --lines 80
```

Share those details with the person administering Mirage for your team when asking for help.
For a test failure, include the relevant GitHub step output. Review any diagnostics before
sharing them, and leave out secrets, tokens, and pairing codes.
