Run memory-heavy or long jobs on GitHub Actions instead of on a small, shared agent computer.
AI agents (and the people working with them) often share one modest Linux box.
One 6 GB data job can push everyone else into swap. This repo gives you a
workflow_dispatch workflow plus a small helper script: you name a job, it runs
on a fresh GitHub-hosted runner, and the results come back to a local folder.
flowchart LR
A[Agent on shared box] -- offload_job.sh job args --> G[gh workflow run]
G --> R[GitHub Actions runner<br/>runs jobs/job.py]
R -- out/ uploaded as artifact --> S[(artifact, 7 days)]
A -- gh run download --> L[results/job_id/]
- Protect the shared box. A simple rule works well: if a job is expected to need more than about 2 GB of memory or run longer than about 30 minutes, run it off-box.
- Clean, repeatable runs. Every job starts on a fresh runner (about 16 GB RAM, 4 vCPUs on standard Linux runners for public repos; 7 GB / 2 vCPUs on private ones).
- Nothing to host. No servers, queues or keys beyond your existing
ghlogin. - A record for free. Each run has a URL, a log, and a
_run_meta.jsonwith exit code, wall time and peak memory. - Measure, don't guess. Each run records peak resident memory (
/usr/bin/time -v) and wall time, so you can size jobs from data before deciding where they run.
Requires the GitHub CLI (gh), logged in with
access to run workflows on your runner repo, plus bash.
- Create a repo from this one (public or private) and push it to GitHub.
- Add your jobs as
jobs/<name>.py. Each job must write its output toout/. Optional dependencies go injobs/requirements.txt. - On the shared box, point the helper at your repo:
export OFFLOAD_REPO=<owner>/<repo>
export OFFLOAD_RESULTS_DIR=~/offload-results # optional, default ./results./offload_job.sh sample_heavy "--gb 3"Output:
dispatched job=sample_heavy job_id=sample_heavy-20261020-232957-10073
run: https://github.com/<owner>/<repo>/actions/runs/<run_id>
status=success artifact_download=ok path=./results/sample_heavy-20261020-232957-10073
{"job":"sample_heavy","job_id":"...","exit_code":0,"wall_seconds":9,"max_rss_kb":3200000}
The script exits 0 only if the run succeeded and the artifact downloaded.
The results folder holds everything the job wrote to out/, plus _log.txt
(stdout/stderr), _time.txt and _run_meta.json.
Exit codes: 2 bad job name, 3 run not found after dispatch, 1 run failed
or download failed.
- The helper generates a unique
job_idand passes it to the workflow, whoserun-nameincludes[job_id]. That lets the helper find its own run even when several are dispatched at once. - The workflow validates the job name (
a-z0-9_-) and job id, then runspython jobs/<job>.py <args>. Arguments are split on whitespace and are never passed througheval. out/is uploaded as artifactresult-<job_id>with 7-day retention, even if the job fails.
- Free minutes. Public repos get standard GitHub-hosted runners free. Private repos draw on your plan's included minutes (2,000/month on GitHub Free at the time of writing); check your billing page before offloading lots of work.
- Time. The workflow caps a job at 300 minutes; GitHub's hard limit is 6 hours per job. Longer work needs a different machine.
- Memory. Standard runners have a fixed memory size. Bigger needs larger (paid) runners or a self-hosted runner.
- Inputs. Jobs see only what is in the repo, what they download, and encrypted secrets. Don't commit private data, and remember that logs and artifacts of a public repo are public.
- Artifacts expire after 7 days. Copy anything you want to keep.
- The helper uses your existing
ghauthentication and never prints tokens. Don't add debug output (such asset -x) that might. - The workflow runs with
contents: readpermissions only. - Treat job output as data. Review it before acting on it.
python3 -m unittest discover -s tests -v
shellcheck offload_job.shThe tests need no network. The ci workflow runs both on every push.
What was tested: the 3 unit tests plus 1 live run. Unit tests cover the sample job and the helper's input checks. Dispatch, run lookup and artifact download were verified with a live run on a test repo.
Built with AI coding assistance. I defined the requirements, ran the tests, and reviewed the changes.
MIT. See LICENSE.