# .env — per-runner environment for a PERSISTENT self-hosted runner # (mandrel-platform runner kit). # # Copy this file to `/.env` (the runner root, next to config.sh) # and replace every `` placeholder with the runner's ABSOLUTE # root path (e.g. /Users/ci/Development/github-runners/myrepo). The runner # loads this file at service start and injects the variables into every job. # # Placeholder convention: `` between angle brackets — search for # `<` after copying to find everything that still needs a value. # # See templates/runbooks/runner-provisioning.md for the full provisioning # procedure, including WHY every value here must be runner-scoped (multiple # runners on one host share an OS user, so anything resolved against $HOME is # a cross-runner concurrency hazard). # Locale — pnpm/git/node emit UTF-8; a C locale garbles their output. LANG=en_US.UTF-8 # Job-start hygiene hook. Runs templates/runner/job-cleanup.sh (installed # into the runner root) before every job: reaps orphaned pnpm/node processes # from THIS runner's work tree and clears stale install/temp artifacts from # `_work/_temp`. The hook is runner-scoped and never fails the job (always # exits 0). # # Runner-scoped means CHEAP as well as safe: the hook runs inside the job's # clock, so anything it reads is billed to `Set up runner` and counts against # the job's `timeout-minutes`. It touches only this runner's own paths, so its # cost never becomes a function of host-wide temp churn (issue #343). ACTIONS_RUNNER_HOOK_JOB_STARTED=/job-cleanup.sh # Job-end reap hook. Runs templates/runner/job-completed.sh (installed into # the runner root) after the last step of every job: terminates whatever of # THIS job's process tree is still alive — SIGTERM, a bounded grace, then # SIGKILL — scoped to processes under this runner's `_work/`. # # Both hooks are needed, and neither substitutes for the other. The started # hook is defence against the PREVIOUS job; it runs before the new job's own # processes exist, so it cannot help a job that is already minutes in. The # completed hook makes each job clean up after itself while the runner still # knows whose processes these are — which is what a CANCELLED job never does # on its own (the runner terminates the step it is executing, not everything # that step forked). Like the started hook, it is runner-scoped and always # exits 0. ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/job-completed.sh # Runner-scoped tool cache. Without this, actions/setup-node & friends # default the tool cache to a host-shared location and co-resident runners # race on extraction. `_work/_tool` is inside this runner's own work tree, # so each runner gets an isolated cache. RUNNER_TOOL_CACHE=/_work/_tool # Same value, second consumer: some toolchain actions read # AGENT_TOOLSDIRECTORY (the Azure Pipelines-era name) instead of # RUNNER_TOOL_CACHE. Keep both pointing at the same runner-scoped dir. AGENT_TOOLSDIRECTORY=/_work/_tool # ── pnpm scoping (read this before enabling the hook) ────────────────────── # The pnpm shim install location is a WORKFLOW-side setting, not a runner-side # one: pnpm/action-setup's `dest` input defaults to `~/setup-pnpm`, which is # SHARED across every runner on the host. The platform's setup-toolchain # composite action already re-defaults `dest` to `${{ runner.temp }}/pnpm` # (runner-scoped); workflows that call pnpm/action-setup directly MUST pass # `dest: ${{ runner.temp }}/pnpm` (or the pr-quality.yml `pnpm-dest` input). # The job-cleanup.sh hook only reaps the runner-scoped locations — it # deliberately never touches `~/setup-pnpm`.