Title: GitHub self-hosted runner cleanup: dirty workspaces
URL: https://codemany.com/problems/dirty-workspace-between-jobs
Description: Leftover files on a GitHub self-hosted runner break the next job. See why workspaces stay dirty, cleanup steps that work, and how single-use runners help.
All pages as Markdown: https://codemany.com/llms.txt

---

[Home](https://codemany.com/) › [Problems](https://codemany.com/problems)

# Dirty workspace between jobs

A self-hosted runner reuses its work folder, so files from one job can break the next. Add cleanup steps, or use single-use runners that start each job fresh.

## The short answer

GitHub self-hosted runner cleanup is your job, not GitHub's. A runner you set up by hand runs job after job in the same folder. What one job leaves, the next one finds. You can clean up with steps and hooks. Or you can give each job a new runner.

## Why files survive between jobs

GitHub's runner keeps a `_work` folder, the GitHub Actions runner workspace. Each repo gets a folder inside it, and that folder stays. `actions/checkout` cleans the repo folder by default. It does not touch files outside it. Tools a job installed stay. Docker images stay. Files in your home folder stay. So do programs a job left running.

## Symptoms: flaky builds and odd failures

- A test passes on GitHub's runners but fails on yours.
- A build uses old output from a past job.
- "Permission denied" on files a Docker step made as root.
- A port is busy because an old server still runs.
- The disk fills up. See [no space left on device](https://codemany.com/problems/self-hosted-runner-disk-full).

These bugs are hard to find. They depend on which job ran before, so they come and go. A rerun often passes, which hides the cause.

## Cleanup steps you can add

Clear the workspace first, and clear temp files last, even when the job fails:

```
steps:
  - name: Clean workspace
    run: |
      shopt -s dotglob
      rm -rf "${GITHUB_WORKSPACE:?}"/*
  - uses: actions/checkout@v4
  - run: make test
  - name: Clean up
    if: always()
    run: rm -rf "${RUNNER_TEMP:?}"/*
```

For GitHub Actions self-hosted runner cleanup across every workflow, use a runner hook. Add this line to the `.env` file in the runner's folder, then restart it:

```
ACTIONS_RUNNER_HOOK_JOB_STARTED=/opt/runner-hooks/clean.sh
```

The script runs before each job. Use it to delete leftover files and stop old processes. Keep it short, since every job waits for it.

## Single-use runners

A single-use runner takes one job, then goes away. GitHub's runner supports this with `./config.sh --ephemeral`. But then you must register a new runner for every job.

Codemany does that for you. Each job gets a fresh [single-use runner](https://codemany.com/features/single-use-runners), registered just in time. When the job ends, the runner exits, Codemany removes it, and its folder is wiped. Downloads from `actions/setup-*` steps stay in a shared tool cache, so jobs don't start cold. You change one line: `runs-on: [self-hosted, codemany]`. Details: [ephemeral runners](https://codemany.com/docs/guides/ephemeral-runners).

A fresh runner keeps jobs apart from each other, not from your machine. Jobs run as your user, so files changed outside the folder stay. Read [runner security](https://codemany.com/docs/guides/runner-security).

## A fresh VM per job on Linux

On Linux with KVM, you can turn on a fresh VM per job. Host mode stays the default. Build the image once, then switch:

```
codemany isolation vm-image
codemany isolation vm
```

Each job then gets Docker and passwordless `sudo` in a VM laid out like GitHub's image. The VM is thrown away after the job, so nothing carries over, not even cloud keys. It is slower, since each job boots a VM first. See [VM per job](https://codemany.com/features/vm-per-job).

## Questions

### Does actions/checkout clean the workspace?

It cleans the repo folder by default, but not files outside it.

### Are Codemany runners reused?

No. Each job gets a new single-use runner, and its job files are removed.

### What about tools a job installs?

In VM mode on Linux, the whole VM is thrown away after the job. In the default host mode, tools a job installs stay on the machine.

## Related

Read next: [ephemeral runners](https://codemany.com/docs/guides/ephemeral-runners), [single-use runners](https://codemany.com/features/single-use-runners), [VM per job](https://codemany.com/features/vm-per-job) and [runner security](https://codemany.com/docs/guides/runner-security).

[Start a 14-day trial](https://codemany.com/docs/get-started) $20 a month for 2 machines. No card needed.
