In GitHub Actions
Run it from a private repo of your own, on GitHub's runners or a machine of yours. On a team, each person keeps their own control repo, so nobody's token sits where a teammate can write; they can all point at the same repos, since each run sees its own token's @me. It's GitHub only and one file: install refuses a Bitbucket config or one that extends a base.
$ cd my-awaywork-repo
$ awaywork schedule install
[...]
◇ Which runner?
│ self-hosted
Wrote .github/workflows/awaywork.yml
Copied ~/.config/awaywork/config.yml to .awaywork/config.yml
Before the first run:
commit .github/workflows/awaywork.yml and .awaywork/config.yml
add these repository secrets: GH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN
GH_TOKEN is a classic personal access token of yours with the `repo`, `workflow` and `read:org` scopes (…)It writes a workflow with a cron at each window's start (none with windows: []) plus workflow_dispatch, copies your config to .awaywork/config.yml and your skills to .awaywork/skills/, and offers to set each needed secret it finds in your environment or env file with gh secret set, never printing values, and naming whose GH_TOKEN it is. When it has secrets to offer, it first checks the account gh acts as (GH_TOKEN, else gh's active one) has write access to the repo; when it hasn't, it stops before writing anything and names it: switch with gh auth switch --user <other> or forge.github.user, or run GH_TOKEN=$(gh auth token --user <other>) awaywork schedule install.
Then commit, add the secrets it lists as unset (GH_TOKEN, provider tokens, and any secrets of sources, repos, MCP servers and notify), and run it once from the Actions tab. schedule remove deletes the workflow.
GH_TOKEN is your classic personal access token: repo reads your PRs, issues, checks and logs and pushes branches and drafts; workflow lets it push branches that touch workflow files; read:org shows teammates with private org membership, whose text would otherwise be hidden.
Details:
- The runner is GitHub-hosted by default: a fresh VM, no Codex. Self-hosted: see Running Codex. Without a terminal,
--with github-actionskeeps the workflow's runner (hosted for a new one) and offers no secrets. - No daemon there, so
run: soonrules run in the windows; install says so once. doctorinside the repo fails naming secrets missing from the repo and its org, and fails a workflow that doesn't parse; when gh can't list secrets, it warns why. The action fails first thing on an emptyGH_TOKEN, the sign secrets didn't reach the job.- The cron is UTC: install converts each start from your timezone at standard time, and the job's
TZkeeps ends,daysand routines local. Runs never start early, but in summer time up to an hour late (a Rome18:00window fires at17:00UTC, 19:00 there March to October), so keep Actions windows over an hour. Changed timezone? Run install again.
The action
The workflow calls awaywork's action at the release you generated it with, AndreaProvenzale/awaywork@v0.3.1 say, which installs that version (version input). On a GitHub-hosted runner it frees about 20 GB of preinstalled SDKs (runners otherwise die quietly on large repos) and makes the worker user.
It commits as the token's user through env, not git config, appends the morning report to the job summary and uploads the run as an artifact. A custom source's setup runs as its own step first, in the config's folder, without secrets.
GITHUB_TOKENis read-only (permissions: contents: read), checkout keeps no credential (persist-credentials: false), and only the action's step gets secrets.- Every step inside the action sees them, hence the pin: new action code meets your secrets only when you rerun install. Pin
uses:to the release's SHA for tighter. The hosted runner's two install steps are the exception: npm packages' install scripts and thescriptsinput get onlyPATH,HOMEandUSER. That stops a careless leak, not a hostile installer, which has sudo beforeawaywork run. - To upgrade, upgrade awaywork and rerun
schedule install --with github-actions; by hand, changeuses:andversiontogether. A workflow from before the action runs its steps inline;doctorwarns. - Rerun install after config or skill changes. A matching file is up to date; one that differs shows the diff and is replaced only on your yes. Without a terminal it keeps the repo's copy, prints the diff and exits 1. The skills folder is replaced whole, dropping repo-only skills; with no skills of yours, the repo's stay.
On the runner
- No state on a hosted runner: no attempts, skips or earlier reports, so unchanged items run again, none is given up on, your local
awaywork skipdoesn't reach it, and a chain starts without the last report. Waiting questions and posts still wait, read off the item, andawaywork approvereplies there. - No push hold: without the head a
deliver: pushleft, the rule pushes again every run itswhereholds. - A self-hosted runner keeps its user's
~/.local/state/awaywork, as on your machine. - MCP servers and toolchains: a stdio MCP server's
commandmust exist on the runner (npxandnodedo). On a hosted runner it runs as the worker, who can't enter the runner's home, so a script in your checkout won't start; install packages, pnpm included, with the action'spackages, not a setup action that installs into the home (pnpm/action-setupdoes). HTTP servers must be reachable from GitHub. - 6 hours: hosted jobs end at 6 hours, so a run stops 5h40 after it starts or at the window's end, leaving 20 minutes to publish; the workflow sets
timeout-minutes: 360, andplanwarns about longer windows (GitHub-hosted runners end a job at 6 hours, so a run stops 5h40 after it starts, before 18:00-07:00 ends). It stops like any window end, Actions-tab runs included. Self-hosted runs last the window, withtimeout-minutes: 1440, added on your next install.
Run one item now
The workflow takes a unit input, an item key:
$ gh workflow run awaywork -f unit=API-12It runs only API-12, outside any window, retrying even after a failed attempt. The item is fetched again and must still match a rule, so "only your items", the ceiling and the prompt hold.
concurrency: awayworkcancels nothing: a dispatch waits for the nightly run, and GitHub keeps only the newest pending run.- It spends daytime quota; as a run outside the windows, workers stop at
present_session_max(40% of the 5-hour window by default). - "Run it when Sentry escalates" needs a relay: as far as we know Sentry's webhooks can't send what GitHub's dispatch API wants. awaywork ships none and holds no credentials for one; a small function calling
gh workflow rundoes it.
Reports from the runner
Each run uploads report.md, report.json and units.json as an artifact like awaywork-20261014-223527. Inside the control repo, awaywork report and report --list fetch those through gh and mix them with your local runs:
$ awaywork report --list
20261015-160012 9 units, 3 draft PRs, 2 reports (github: awaywork)
20261012-203418 0 units, 0 draft PRs, 0 reportsThey find runs by the workflow's file name, so keep its name: as written. A run downloads once, into ~/.local/state/awaywork/remote/<owner>/<repo>/<artifact>/, never runs/, so stats, replay and attempts stay local. Artifacts expire with the repo's retention (90 days by default). Offline you get a warning: and your local runs; outside the control repo, local runs only.
Credentials on a runner
| Provider | Secret | Notes |
|---|---|---|
| Claude Code | CLAUDE_CODE_OAUTH_TOKEN | From claude setup-token; lasts a year |
| Codex | none | Self-hosted runners only, on the machine's own login |
| Cursor | CURSOR_API_KEY | A key from Cursor's dashboard |
| Your own CLI | its secrets | Self-hosted runners only |
Running Codex
Codex (experimental) uses the machine's codex login, which it rotates in place, so a fresh runner has none to keep. With a hosted runner, install leaves it out and runs route to your other providers:
codex won't run in this workflow: a GitHub-hosted runner has no Codex login, and one copied there goes stale since Codex rotates it in place; run Codex locally or on a self-hosted runnerRun it locally (On your machine), or on a machine of yours added as the control repo's runner, logged in with codex login as the runner's user (codex login --device-auth on a headless box, Codex's docs). Pick self-hosted at Which runner?: the workflow says runs-on: self-hosted and keeps Codex. Details:
- Every self-hosted runner the repo can use matches that label, an organization's too, so give yours a label of its own:
runs-on: [self-hosted, alice-mac]. Install nags while it's plainself-hostedand keeps labels on a rerun. - There the action keeps its hands off: no disk freeing, no CLIs installed over yours, no git config. It installs awaywork alone into the job's temp dir and runs the
codex,claudeandagenton the runner'sPATH; Claude still readsCLAUDE_CODE_OAUTH_TOKEN. - A runner installed as a service doesn't read your shell profile: its
PATHis the.pathfile./config.shsaved, exported byrunsvc.sh, other variables in.env. nvm or Homebrew binaries may be missing: configure it wherecodex --versionworks, or edit.pathand restart (./svc.sh stop,./svc.sh start). - GitHub recommends self-hosted runners only for private repos, since a fork's PR could run code on them: keep the control repo private.
Running Cursor
Cursor's CLI has no npm package, so on a hosted runner the action installs it with Cursor's installer, as the runner's user, into /opt/cursor (readable, not writable, by the worker), linked as /usr/local/bin/agent. Install puts it in the workflow's scripts input and names CURSOR_API_KEY among the secrets:
packages: "@anthropic-ai/claude-code"
scripts: |
sudo install -d -o "$USER" /opt/cursor
curl -fsS https://cursor.com/install | HOME=/opt/cursor bash
sudo ln -sf "$(readlink -f /opt/cursor/.local/bin/agent)" /usr/local/bin/agent
agent --versionSelf-hosted runs the agent on the runner's PATH. Either way it needs the CURSOR_API_KEY secret: a machine login doesn't reach its fresh HOME.