Skip to content

Add systemd --user backend for WorkSweep scheduling on Linux - #1692

Open
takanorinishida wants to merge 2 commits into
danielmiessler:mainfrom
takanorinishida:linux-worksweep-systemd
Open

Add systemd --user backend for WorkSweep scheduling on Linux#1692
takanorinishida wants to merge 2 commits into
danielmiessler:mainfrom
takanorinishida:linux-worksweep-systemd

Conversation

@takanorinishida

@takanorinishida takanorinishida commented Jul 29, 2026

Copy link
Copy Markdown

Summary

WorkSweep.ts (the hourly Work System sweep — session catch-up, stale-issue flagging, project checks, TELOS-goal derivation) is already platform-neutral: it only uses existsSync/readFileSync/Bun.spawn, no macOS-specific calls. But InstallWorkSweep.ts, the script that schedules it, only knew how to talk to launchd. On Linux there was no way to get WorkSweep running on a recurring cadence at all.

This PR adds a systemd --user backend selected via process.platform, mirroring the pattern LIFEOS/PULSE/manage.sh already uses for Pulse itself (launchd on darwin, systemctl --user on linux). Two new unit templates pair with the existing plist template:

  • com.lifeos.worksweep.service.template — oneshot service that runs WorkSweep.ts, same log target (MEMORY/STATE/com.lifeos.worksweep.log) as the launchd StandardOutPath/StandardErrorPath
  • com.lifeos.worksweep.timer.templateOnBootSec=2min + OnUnitActiveSec=60min + Persistent=true, reproducing the plist's RunAtLoad + StartInterval 3600 behavior

InstallWorkSweep.ts now branches on process.platform: the existing darwin/launchd code path is untouched, and a new linux/systemd path materializes both templates into ~/.config/systemd/user/, runs daemon-reload, calls loginctl enable-linger (same survives-logout requirement already documented for com.lifeos.pulse), and enable --nows the timer. --uninstall and --status get matching linux branches.

Update: a code review caught that the loginctl enable-linger call originally read the username from process.env.USER, which isn't guaranteed to be set in every invoking environment (some non-interactive shells don't inherit it) — an empty string would make the call fail. Fixed by adding a username() helper (id -un, mirroring the existing uid() helper's id -u) and using that instead. Re-verified end to end after the fix (see testing evidence below).

Testing evidence

Verified end-to-end on a real Linux host (Ubuntu, systemd):

$ bun ~/.claude/LIFEOS/TOOLS/InstallWorkSweep.ts
[InstallWorkSweep] detected bun at /home/.../bun
[InstallWorkSweep] wrote /home/.../.config/systemd/user/com.lifeos.worksweep.service
[InstallWorkSweep] wrote /home/.../.config/systemd/user/com.lifeos.worksweep.timer
[InstallWorkSweep] systemd timer enabled — com.lifeos.worksweep.timer active
NEXT                        LEFT     LAST  PASSED UNIT                       ACTIVATES
Wed 2026-07-29 00:55:48 UTC 59min... -      -      com.lifeos.worksweep.timer com.lifeos.worksweep.service

$ systemctl --user status com.lifeos.worksweep.service
● com.lifeos.worksweep.service ...
   Process: ... ExecStart=.../bun .../WorkSweep.ts (code=exited, status=0/SUCCESS)

$ bun ~/.claude/LIFEOS/TOOLS/WorkSweep.ts
[WorkSweep] repo=<redacted> since=24h max-create=50 APPLY
+ created #17 [Goal] ... [goal:G0]
+ created #18 [Goal] ... [goal:G1]
+ created #19 [BPE] Subtraction pass due — never run [bpe-audit-due]
[WorkSweep] done in 5092ms — sessions=0 new=0 stale=0 project-checks=0 goals=2 bpe=1

Also confirmed --uninstall cleanly disables + removes both unit files and --status reports the timer's next-run time. The macOS/launchd path is unchanged — only new if (IS_LINUX) branches were added, so no risk of regression for existing darwin installs.

After the username() fix: re-ran install and --status, both succeeded, and loginctl show-user confirmed Linger=yes.

🤖 Generated with Claude Code

takanorinishida and others added 2 commits July 29, 2026 00:19
InstallWorkSweep.ts only supported launchd, so the hourly WorkSweep
sub-sweeps (session catch-up, stale flagging, project checks, TELOS
goal derivation) had no way to run on Linux even though WorkSweep.ts
itself is already platform-neutral (existsSync/readFileSync/Bun.spawn,
no macOS-specific calls).

Adds a systemd --user code path selected via process.platform, mirroring
the pattern PULSE/manage.sh already uses for Pulse itself (launchd on
darwin, systemd --user on linux). Two new unit templates
(com.lifeos.worksweep.service.template + .timer.template) pair with the
existing plist template; OnBootSec=2min + OnUnitActiveSec=60min +
Persistent=true reproduce the plist's RunAtLoad + StartInterval 3600
behavior. Enables linger for the user so the timer survives logout,
same requirement documented for com.lifeos.pulse.

Verified end-to-end on a Linux host: install/--status/--uninstall all
work via `systemctl --user`, and a live run of WorkSweep.ts under the
resulting timer created labeled issues successfully.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
process.env.USER isn't guaranteed to be set in every environment that
might invoke this installer (some non-interactive shells don't inherit
it), which would pass an empty string to `loginctl enable-linger ""`
and fail. Adds a `username()` helper (id -un, mirroring the existing
uid() helper's `id -u`) and uses it instead — reliable regardless of
the calling environment.

Verified on the same Linux host as the original PR: re-ran `install`
and `--status` after the change, both succeeded, and
`loginctl show-user` confirmed `Linger=yes`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant