github-self-hosted-runner
Skill5 starsChanged 45 days agoMITView on GitHub
Create one isolated runner instance per repository or organization registration. Detect the operating system first and use the matching service manager: systemd on Linux or launchd on macOS. Determine the account that should own and run the runner instead of assuming a fixed username or home directory. On Linux, resolve the account and paths with: On macOS, resolve the account without relying on getent: Validate that the values are non-empty and RUNNER_HOME exists. When commands run through sudo, do not…
- Reads credentials
In this repository15 other files it gives its agents.
- AGENTS.mdAGENTS.md
- development/AGENTS.mdAGENTS.md
- cli-designSkill
- codebase-landing-copySkill
- context-file-tuneupSkill
- course-notesSkill
- decision-recordsSkill
- feature-mapSkill
- github-pr-fixupSkill
- oss-marketingSkill
- oss-repo-readinessSkill
- session-log-auditSkill
- technical-manualSkill
- verify-bugSkill
- whiteboardSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
28 rules. About 0 are common, 17 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈17
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 13
Create one isolated runner instance per repository or organization registration.
- Distinctive: line 65
Treat credentials as secrets:
- Distinctive: line 67
Never print a token.
- Distinctive: line 68
Never include it in a service unit, repository, skill file, or persistent environment file.
- Very specific: line 70
Put it in a mode-0600 temporary file only for the registration operation, then securely delete it.
- Distinctive: line 82
Check whether the destination directory, runner registration, or service already exists.
- Distinctive: line 84
Check for active jobs before stopping, replacing, or removing an existing runner.
- Very specific: line 86
Ensure curl, tar, python3, and the platform service manager (systemd on Linux, launchd on macOS) are available.
- Distinctive: line 89
If the instance already exists and is healthy, report that instead of creating a duplicate.
- Very specific: line 111
Always use a fresh runner distribution for a new instance.
- Very specific: line 116
Run config.sh as $RUNNER_USER from inside the instance directory.
- Very specific: line 135
Do not use --replace to overwrite an unrelated live runner.
- Distinctive: line 177
Before replacing an existing template, inspect it and confirm its user and root directory match the resolved values.
- Very specific: line 183
Create a per-instance LaunchDaemon plist at /Library/LaunchDaemons/com.github.actions.runner.<slug>.plist.
- Distinctive: line 194
Do not configure a Linux runner with systemd on macOS or a macOS runner with launchd on Linux.
- Distinctive: line 199
Verify the matching service manager.
- Very specific: line 216
Read .runner using UTF-8 with BOM support and confirm:
- Distinctive: line 238
Use separate operating system users or separate machines for repositories that should not trust each other's workflow code or files.
- Very specific: line 251
Run ./config.sh remove --token '<removal-token>' from the runner directory as $RUNNER_USER.
- Distinctive: line 258
Never delete the shared Linux systemd template while other instances use it.
- Distinctive: line 263
Use this when removing every runner managed by this skill from the machine.
- Very specific: line 269
Stop and disable each service.
- Very specific: line 279
Remove only the matching service configuration.
- Very specific: line 284
Delete the explicit runner instance directories only after GitHub removal succeeds and the user confirms deleting the runner data.
- Distinctive: line 289
Verify that the runner registrations are gone from GitHub, the services are absent or inactive, and no targeted instance directories or …
- Distinctive: line 300
If registration fails, do not enable a restart-looping service.
- Distinctive: line 306
If extraction or checksum verification fails, remove the incomplete destination or clearly mark it incomplete.
- Distinctive: line 308
If the service starts but does not listen, inspect its journal on Linux or runner diagnostic logs on macOS.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
GitHub self-hosted runner
Create one isolated runner instance per repository or organization registration. Detect the operating system first and use the matching service manager: systemd on Linux or launchd on macOS. Determine the account that should own and run the runner instead of assuming a fixed username or home directory.
On Linux, resolve the account and paths with:
RUNNER_USER="${RUNNER_USER:-${SUDO_USER:-$USER}}"
RUNNER_GROUP="$(id -gn "$RUNNER_USER")"
RUNNER_HOME="$(getent passwd "$RUNNER_USER" | cut -d: -f6)"
RUNNER_ROOT="$RUNNER_HOME/actions-runners"On macOS, resolve the account without relying on getent:
RUNNER_USER="${RUNNER_USER:-${SUDO_USER:-$USER}}"
RUNNER_GROUP="$(id -gn "$RUNNER_USER")"
RUNNER_HOME="$(dscl . -read "/Users/$RUNNER_USER" NFSHomeDirectory | awk '{print $2}')"
RUNNER_ROOT="$RUNNER_HOME/actions-runners"Validate that the values are non-empty and RUNNER_HOME exists. When commands
run through sudo, do not accidentally use root's $USER or $HOME as the
runner account.
Namespace every instance by target slug:
- Directory:
$RUNNER_ROOT/<slug> - Runner name:
<hostname>-<slug> - Custom label:
<slug> - Linux service:
github-actions-runner@<slug>.service - macOS launchd label:
com.github.actions.runner.<slug>
For example, https://github.com/owner/blog uses directory $RUNNER_ROOT/blog,
runner name <hostname>-blog, and label blog.
Required inputs
Obtain:
- A repository URL such as
https://github.com/owner/repository, or an organization URL such ashttps://github.com/organization. - A GitHub credential authorized to manage self-hosted runners:
- classic PAT with
reposcope for a repository, or - an appropriately scoped fine-grained token.
- classic PAT with
GitHub does not support account-wide runners for a personal user URL. If a one-segment GitHub URL belongs to a personal account rather than an organization, ask for a repository URL.
Treat credentials as secrets:
- Never print a token.
- Never include it in a service unit, repository, skill file, or persistent environment file.
- Put it in a mode-
0600temporary file only for the registration operation, then securely delete it. - Recommend rotating any token pasted into chat after setup.
Preflight
- Detect the hostname and architecture.
- Convert architecture names for GitHub assets:
x86_64→x64aarch64orarm64→arm64
- Derive a lowercase filesystem-safe slug from the repository name or
organization name. Allow only letters, digits,
_, and-. - Check whether the destination directory, runner registration, or service already exists.
- Check for active jobs before stopping, replacing, or removing an existing runner. Do not interrupt a job without user confirmation.
- Ensure
curl,tar,python3, and the platform service manager (systemdon Linux,launchdon macOS) are available.
If the instance already exists and is healthy, report that instead of creating a duplicate. If reconfiguration is requested, unregister the old instance before replacing it.
Install the runner
Fetch release metadata from GitHub's official API:
curl -fsSL https://api.github.com/repos/actions/runner/releases/latestSelect the asset matching both operating system and architecture, named like
actions-runner-linux-<arch>-<version>.tar.gz on Linux or
actions-runner-osx-<arch>-<version>.tar.gz on macOS. Read its
browser_download_url and digest. Download to a temporary archive, verify the
SHA-256 digest, and only then extract it into $RUNNER_ROOT/<slug>.
Set ownership recursively to $RUNNER_USER:$RUNNER_GROUP (on macOS, use the
resolved group, commonly staff). Delete the temporary archive even when a
command fails.
Always use a fresh runner distribution for a new instance. Do not copy
.runner, .credentials, _diag, or _work from another instance.
Register
Run config.sh as $RUNNER_USER from inside the instance directory. The
working directory matters because runner scripts use relative paths.
Use:
./config.sh \
--url '<github-url>' \
--pat "$GITHUB_TOKEN" \
--unattended \
--name '<hostname>-<slug>' \
--labels '<slug>' \
--replacePass the PAT from the protected temporary file and securely remove that file immediately afterward. Do not persist the PAT; GitHub exchanges it for runner credentials during registration.
Do not use --replace to overwrite an unrelated live runner. Confirm that an
existing runner with the same name belongs to this VM and target.
Configure the service manager
Linux: systemd
Render a reusable template unit at
/etc/systemd/system/github-actions-runner@.service using the resolved runner
account values. Expand shell variables while writing the unit; systemd does not
expand them in these fields:
sudo tee /etc/systemd/system/github-actions-runner@.service >/dev/null <<EOF
[Unit]
Description=GitHub Actions self-hosted runner (%i)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${RUNNER_USER}
Group=${RUNNER_GROUP}
WorkingDirectory=${RUNNER_ROOT}/%i
ExecStart=${RUNNER_ROOT}/%i/run.sh
Restart=always
RestartSec=5
KillSignal=SIGINT
TimeoutStopSec=90
[Install]
WantedBy=multi-user.target
EOFThen run:
sudo systemctl daemon-reload
sudo systemctl enable --now github-actions-runner@<slug>.serviceBefore replacing an existing template, inspect it and confirm its user and root directory match the resolved values. Do not disrupt instances owned by another account.
macOS: launchd
Create a per-instance LaunchDaemon plist at
/Library/LaunchDaemons/com.github.actions.runner.<slug>.plist. Set UserName,
GroupName, WorkingDirectory, and ProgramArguments to the resolved account,
group, and instance path. Use <RUNNER_ROOT>/<slug>/run.sh as the program, set
RunAtLoad and KeepAlive to true, and do not put credentials in the plist.
Make the plist root-owned with mode 0644, then load it with
sudo launchctl bootstrap system <plist-path>. Use
sudo launchctl kickstart -k system/<label> to start or restart the instance
after confirming it has no active job. If the service is already bootstrapped,
inspect it before replacing it; do not unload unrelated runners.
Do not configure a Linux runner with systemd on macOS or a macOS runner with launchd on Linux.
Verify
Verify the matching service manager. On Linux:
systemctl is-enabled github-actions-runner@<slug>.service
systemctl is-active github-actions-runner@<slug>.service
journalctl -u github-actions-runner@<slug>.service --no-pager -n 20On macOS:
sudo launchctl print system/com.github.actions.runner.<slug>
tail -n 20 '<RUNNER_ROOT>/<slug>/_diag/Runner_*.log'The runner log must show Connected to GitHub and Listening for Jobs.
Read .runner using UTF-8 with BOM support and confirm:
agentNameis<hostname>-<slug>gitHubUrlis the requested targetworkFolderis_work
Tell the user they can target the instance with:
runs-on: [self-hosted, <slug>]Report the runner name, directory, label, service, version, and active/listening status. Do not repeat the token.
Multiple runners on one VM
Each runner needs its own directory, local credentials, listener process, label, and platform service instance. Multiple instances can run concurrently but share the machine's CPU, memory, disk, network, and—when run as the same user— filesystem trust boundary.
Use separate operating system users or separate machines for repositories that should not trust each other's workflow code or files.
Removal or renaming
To remove or rename an instance:
- Confirm it is not running a job.
- Disable and stop the platform service:
systemctl disable --now github-actions-runner@<slug>.serviceon Linux, orlaunchctl bootout system/<label>on macOS. - Obtain a short-lived removal token through GitHub's repository or organization runner API using the PAT.
- Run
./config.sh remove --token '<removal-token>'from the runner directory as$RUNNER_USER. - For a rename, register again with the new runner name and restart the service.
- For permanent removal, delete only that explicit instance directory after registration removal succeeds and the user confirms deletion.
Never delete the shared Linux systemd template while other instances use it. On macOS, remove only the specific instance plist after unloading that instance.
Full uninstall
Use this when removing every runner managed by this skill from the machine. First inventory the registrations, instance directories, and services or plists; do not assume every runner under a shared directory belongs to this setup.
- Confirm that no runner is executing a job. Do not stop a busy runner without user confirmation.
- Stop and disable each service. On Linux, run
sudo systemctl disable --now github-actions-runner@<slug>.servicefor every instance, then confirm those units are inactive. On macOS, runsudo launchctl bootout system/com.github.actions.runner.<slug>for every loaded instance, then confirm those labels are no longer present. - For each instance, obtain a short-lived removal token from its GitHub
repository or organization, then run
./config.sh remove --token '<removal-token>'from that instance directory as$RUNNER_USER. Remove the token from memory and securely delete any temporary credential file. - Remove only the matching service configuration. On Linux, remove
/etc/systemd/system/github-actions-runner@.serviceonly after confirming that no other runner instances use it, then runsudo systemctl daemon-reload. On macOS, remove each matching plist from/Library/LaunchDaemons/after it has been unloaded. - Delete the explicit runner instance directories only after GitHub removal
succeeds and the user confirms deleting the runner data. This removes
binaries, credentials, diagnostics, and workspaces. Preserve unrelated files
in
$RUNNER_ROOT; remove that parent directory only if it is empty and the user confirms it should be removed. - Verify that the runner registrations are gone from GitHub, the services are absent or inactive, and no targeted instance directories or plists remain.
This procedure does not delete $RUNNER_USER or its home directory. Remove an
OS account only if it was created specifically for these runners, it owns no
other data or services, and the user explicitly requests account deletion. Never
remove system-wide dependencies such as Homebrew, Xcode, or command-line tools
as part of runner cleanup.
Failure handling
- If registration fails, do not enable a restart-looping service.
- Preserve downloaded binaries when useful for retry, but remove all temporary credential files.
- A
404from an organization registration endpoint often means the supplied one-segment URL is a personal account, the token lacks access, or the target is incorrect. Verify the target type before retrying. - If extraction or checksum verification fails, remove the incomplete destination or clearly mark it incomplete; never start it.
- If the service starts but does not listen, inspect its journal on Linux or runner diagnostic logs on macOS; verify directory ownership, registration files, network access, and the service's working directory.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

