Don’t let your CI runner agent hoard environment variables
Technical Briefing | 9/26/2026
CI runners are glorified shell factories. You expect them to be pristine for every build, but they love to collect environment baggage from previous jobs. If you are using a persistent runner service, the process tree inherits whatever mess the last execution left behind. This bit me in prod when a sneaky proxy variable set by a specific integration test started breaking legitimate API calls in unrelated pipelines two hours later.
Why process inheritance bites you
Most runners spawn sub-shells to execute your scripts. By default, they do not clear the environment between tasks. If your test suite exports a variable like DB_HOST or PROXY_URL, it stays in the memory space of the runner process. Even if you unset it in your next script, you’ve already introduced a window where the variable was visible to every sub-process. The runner’s parent process is the source of the rot.
pgrep -a -u gitlab-runner | grep -v 'runner-worker' | xargs -I {} sh -c 'strings /proc/$(echo {} | cut -d" " -f1)/environ | grep -E "(PROXY|SECRET|DB)"'
- Check /proc/[pid]/environ to see what the runner is currently leaking
- Use shell wrappers to sanitize the environment before the main script runs
- Restart the runner service after X jobs if you can’t guarantee cleanup
Cleaning house with Ansible
If you are using Ansible to configure these runners, stop setting global variables in .bashrc or /etc/environment. Instead, push a dedicated wrapper script that forces a clean state. Injecting an env -i call ensures your CI commands start from a known baseline rather than inheriting the garbage left over from that failed Selenium run last night. If the pipeline needs custom variables, pass them explicitly through the CI provider’s config instead of relying on the host environment.
Next time you see a pipeline fail for no reason, look at the environment, not the code. Chances are the runner is just acting on bad memories from its past life. If you find yourself debugging cross-contamination, just kill the process and let the daemon spawn a fresh one; sometimes the only clean slate is a new PID.
