On your machine
awaywork schedule install # macOS: launchd
awaywork schedule install --with systemd # Linux: a user service
awaywork schedule install --with cron
awaywork schedule status # the job, the daemon and its log
awaywork schedule removeThe job is awaywork watch, kept alive by the scheduler. It checks the clock every minute and starts a run when a window opens, even after the machine slept through the opening, and rereads the config each time, so window edits need no reinstall. It also runs run: soon rules within minutes.
| Scheduler | Writes | Keeps it alive |
|---|---|---|
| launchd | ~/Library/LaunchAgents/awaywork.plist | Started at login, restarted after a crash (KeepAlive, SuccessfulExit: false) |
| systemd | ~/.config/systemd/user/awaywork.service | Enabled at login, Restart=on-failure |
| cron | two tagged lines in your crontab | Started at boot (@reboot) and again every 5 minutes |
$ awaywork schedule status
Scheduled with launchd, keeping awaywork watch running
Config: ~/.config/awaywork/config.yml
Log: ~/.local/state/awaywork/schedule.log
Daemon: running, pid 67014, since 2026-10-25 17:26:00
Run in progress: soon run 20261025-162625
Next poll of flaky: 2026-10-25 17:30:00, backing off after 1 failed poll
Next window: none, no windows set
Daemon log: ~/.local/state/awaywork/watch.logThe daemon's log, watch.log, has events only: start and stop, runs and units starting and ending, and errors; past 5 MB it moves to watch.log.1. A run's full output goes to run.log in its run dir, and into watch.log too with watch: { debug: true }. schedule.log catches only a crashed daemon. The morning report is awaywork report. Both list what failed before any unit started:
warning: source chores: Command failed with exit code 1: ./chores.sh
Error: CHORES_TOKEN is not setDetails:
- One opening, one run. A run that finishes early isn't restarted in the window; one started inside a window, after a reboot say, picks up there, skipping items already worked on.
- One daemon at a time: another copy exits with
awaywork watch is already running (pid 67014), which makes cron's restart line harmless. - Your run or the daemon's. A manual
awaywork runduring a daemon run exits 1 (awaywork watch holds the run lock (pid 67014); try again once its runs end, awaywork schedule status lists them). A window opening during your manual run waits for it, checking every minute and logging it once; a soon run waits for the next poll. A dead process's lock is taken over;--dry-runtakes none. PATH: the job keeps the installing shell's, so it finds the sameclaude,codex,agentandgh, but runs the binaries, not shell functions: pin logins in the config instead.- Secrets come from
~/.config/awaywork/env. The daemon reads it and its own code once, at start: after editing it or upgrading, runschedule installagain, which restarts it. - Restarting:
installandremovestop the running daemon first, whoever started it: a SIGTERM, then up to 2 minutes. A running unit stops, publishes nothing and isn't counted as an attempt. If it's still running after that, install changes nothing and says so. Run from inside the daemon (a hook, a worker), they refuse. - launchd and systemd restart it only after a crash; a SIGTERM of yours keeps it down until your next login or
install. Under cron it's back within 5 minutes. - systemd user services run only while you're logged in (
loginctl enable-lingerlifts that). Without a user session (sudo -iu,su -)--with systemdfails and changes nothing, andstatusanddoctorreport no schedule; log in directly or use cron. windows: []is fine: the daemon idles and install sets no wake.- A failed command gets two lines in the log: its error and the last error line of its stderr. Configured secrets show as
***, so the report is safe fornotify. Run ids are the start time in UTC. - A job from an older awaywork (one trigger per window running
awaywork run --scheduled) still works, butstatusanddoctorflag it: runschedule installonce.
Soon runs
Every watch.every (5 minutes), the daemon polls the sources its run: soon rules read, plans just those rules and starts a run for the units due: new items, or ones changed since their last attempt, like a PR gone red or your reply to awaywork's questions. Here a source's item came in, awaywork's questions on it didn't count, the reply did, and a second source was down for a while:
2026-10-25 16:17:51 awaywork watch started, pid 27290, config ~/.config/awaywork/config.yml
2026-10-25 16:17:51 source flaky: poll failed, next at 2026-10-25 16:21:00: Command failed with exit code 1: ./flaky.sh
2026-10-25 16:17:51 soon run 20261025-151751 started for ch-1
[...]
2026-10-25 16:21:00 source flaky: polled fine again, every 2m
2026-10-25 16:22:00 soon run 20261025-152200 started for ch-1Details:
- awaywork's own questions comment isn't a change, nor is anything a run already took: a failed unit waits for the item to change, or the next window. One its run never attempted (no quota, sat out a present run, stopped) is due again an hour later, or at the first poll after a restart.
- A clock-driven job, Mondays at 09:00, is a routine, which a soon rule starts at the first poll after.
- Each poll that finds units makes one run, with its own run dir, report and
per_runshare. One that finds nothing writes nothing (one line withdebug). - Outside a window a soon run is present: workers stop at
present_session_maxand providers without a quota reading sit out. Inside one it's away and ends with the window. - A poll reads no quota and runs no model. A GitHub poll is 3 searches (your PRs, review requests, assigned issues), one per
searchentry and two more for finished items: about 36 an hour at 5 minutes. A tracker poll is its list call plus a PR search per open task without a link. Give a slow or rate-limited tracker its ownevery. - A failed poll backs off as long as
Retry-Aftersays (Jira and Asana, on a 429), else doubling per failure up to an hour, logging each backoff and recovery. GitHub, Linear and custom sources give no reset time, so they double. notifygets a soon run'sunitevents, not the run's end. A unit that sat out or was passed over bywhenorquotafires none, so hourly retries stay quiet; a failedsetupor a timed-outwhenstill fires.- Runs overlap, never on one item, and one worker runs at a time: a soon unit waits for the window run's current chain, not the whole run, routing on the quota it read when planning. A unit another run holds is skipped as
running in run 20261025-010000. The daemon holds the run lock, and keeps the machine awake, while any run is in progress. - An
isolationedit applies to the next run that starts with none in progress, logged once while it waits. Restartwatchor rerunschedule installto apply it now.
Waking up for it
A sleeping machine runs nothing. While a run is in progress the daemon keeps the machine from idling to sleep (caffeinate -i on macOS, systemd-inhibit --what=idle on Linux where it works); the wake itself is a separate step schedule install offers in a terminal:
◆ Wake this Mac when the window opens (sudo pmset repeat wake MTWRFSU 01:00:00)?
│ ● Yes / ○ No
└- macOS:
pmsetkeeps one repeating wake, so every window must start at the same time, and it replaces your other repeating power events, scheduled sleeps and shutdowns too: the question lists them and defaults to No unless all it replaces is a wake. Keep a laptop on the charger; lid closed on battery, it may not wake fully. - Linux: a root timer,
/etc/systemd/system/awaywork-wake.timerwithWakeSystem=true. A background job can block idle but not sleep, so turn off a desktop's own suspend timer on AC power. - sudo needs a password, so without a terminal install only says how.
schedule removeleaves the wake and prints how to remove it. - Moving the windows' start needs
schedule installagain to move the wake;doctorwarns until then.