Skip to content

feat(linux): add systemd --user backend to 4 scheduler installers - #1704

Open
takanorinishida wants to merge 1 commit into
danielmiessler:mainfrom
takanorinishida:feature/linux/scheduler-installers
Open

feat(linux): add systemd --user backend to 4 scheduler installers#1704
takanorinishida wants to merge 1 commit into
danielmiessler:mainfrom
takanorinishida:feature/linux/scheduler-installers

Conversation

@takanorinishida

Copy link
Copy Markdown

What's broken

DerivedSync.ts / CommitmentSweep.ts / UsageAggregator.ts / CodexUpdate.ts have no macOS dependency in their service bodies (they're plain bun scripts), but their installers only know how to write a launchd plist — so none of the 4 can be scheduled on Linux.

DerivedSync in particular has real user-visible impact: it's what keeps PRINCIPAL_TELOS.md (imported by CLAUDE.md) and LIFEOS_STATE.json (read by the statusline rings) in sync with hand-edited TELOS files. Without it, those two files silently go stale after every TELOS edit.

Fix

Follows the process.platform dispatch pattern InstallWorkSweep.ts established (#1692, closed/ported): darwin path untouched, linux path materializes a systemd --user unit pair instead of a plist.

  • derivedsync: WatchPaths (7 dirs) + RunAtLoad.path unit (PathModified x7) + .service (oneshot); the service is started once at install time to match RunAtLoad's "fire once when loaded" behavior
  • commitmentsweep: StartCalendarInterval 07:00 → .timer (OnCalendar, Persistent=true) + .service
  • usage-aggregator: StartCalendarInterval 03:30 + RunAtLoad.timer (OnCalendar + OnBootSec=2min) + .service
  • codexupdate: StartCalendarInterval 04:00, no RunAtLoad.timer (OnCalendar only) + .service

ThrottleInterval becomes StartLimitIntervalSec/StartLimitBurst on the .service (not on derivedsync's .pathTriggerLimitIntervalSec needs systemd 255+, unavailable on e.g. Debian 12's 252).

InstallCommitmentSweep.ts's darwin plist hardcodes /opt/homebrew/bin/bun (no detectBun()) — left untouched. Only the new linux path calls a newly-added detectBun() to resolve {{BUN}} in the systemd templates.

Also fixes BackgroundServices.md's now-inaccurate "macOS-only" claim in the Fresh Install section.

Why this doesn't change macOS behavior

Every deletion in this diff is either a const moved a few lines up (LABEL, STATE_DIR, COMMAND_TIMEOUT_MS — same value, same file, just relocated next to the other platform-specific paths) or a dispatch line that now ternaries into the same original function, renamed *Darwin with its body byte-for-byte unchanged. No darwin behavior is touched.

Verification

Tested end-to-end on Ubuntu 24.04 / systemd 255:

  • systemd-analyze --user verify passes clean on all 8 new units
  • DerivedSync: install → .path unit active → touching TELOS.md fired a targeted re-run (confirmed via the tool's own derived-sync.jsonl log — only the actions tied to the changed file ran) → PRINCIPAL_TELOS.md regenerated. Re-running install is idempotent. --uninstall removes both units cleanly.
  • UsageAggregator: install → the boot-equivalent run fired immediately, exited 0, wrote usage-daily.jsonl. Timer correctly queued for the next 03:30.
  • CommitmentSweep: install → timer correctly queued for the next 07:00; service not run (matches RunAtLoad false).
  • CodexUpdate: timer registered for 04:00; service deliberately left un-started in this test (it has global-install side effects) — systemctl --user is-active confirms inactive.

Unrelated pre-existing bug found during verification, not fixed here (out of scope — it's in DerivedSync.ts's own logic, not the installer/scheduler): DeriveDenyHashes.ts fails with ENOENT because ~/.claude/skills/_LIFEOS/ doesn't exist on this host. Would fail identically under launchd.

Related: #1692 (WorkSweep systemd backend, same pattern, ported).

🤖 Generated with Claude Code

DerivedSync/CommitmentSweep/UsageAggregator/CodexUpdate are Linux-clean at
runtime (no macOS dependency in the service bodies) but their installers
only knew how to write a launchd plist, so none of the 4 could be
scheduled on Linux. DerivedSync in particular has real user-visible impact
— it's what keeps PRINCIPAL_TELOS.md (CLAUDE.md @-import) and
LIFEOS_STATE.json (statusline rings) in sync with hand-edited TELOS
files; without it those two files silently go stale.

Follows the process.platform dispatch pattern InstallWorkSweep.ts
established (PR danielmiessler#1692, ported-awaiting-release): darwin path unchanged,
linux path materializes a systemd --user unit pair instead of a plist.

- derivedsync: WatchPaths (7 dirs) + RunAtLoad → .path unit (PathModified
  x7) + .service (oneshot), service started once at install to match
  RunAtLoad
- commitmentsweep: StartCalendarInterval 07:00 → .timer (OnCalendar,
  Persistent=true) + .service
- usage-aggregator: StartCalendarInterval 03:30 + RunAtLoad → .timer
  (OnCalendar + OnBootSec=2min) + .service
- codexupdate: StartCalendarInterval 04:00, no RunAtLoad → .timer
  (OnCalendar only) + .service

ThrottleInterval becomes StartLimitIntervalSec/StartLimitBurst on the
.service (not on derivedsync's .path — TriggerLimitIntervalSec needs
systemd 255+, unavailable on e.g. Debian 12's 252).

InstallCommitmentSweep.ts's darwin plist hardcodes /opt/homebrew/bin/bun
(no detectBun()) — left untouched; only the new linux path calls the
newly-added detectBun() to resolve {{BUN}} in the systemd templates.

Every deletion in this diff is either a const moved a few lines (LABEL,
STATE_DIR, COMMAND_TIMEOUT_MS — same value, same file) or a dispatch line
that now ternaries into the same original function (renamed *Darwin,
content byte-for-byte unchanged).

Verified on this host (Ubuntu 24.04, systemd 255):
- `systemd-analyze --user verify` passes clean on all 8 new units
- DerivedSync: install → .path unit active → touching TELOS.md fired a
  targeted re-run (only the changed file's actions, confirmed via the
  tool's own derived-sync.jsonl log) → PRINCIPAL_TELOS.md regenerated;
  re-run is idempotent; --uninstall removes both units cleanly
- UsageAggregator: install → boot-equivalent run fired immediately,
  exited 0, wrote usage-daily.jsonl; timer correctly queued for next
  03:30
- CommitmentSweep: install → timer correctly queued for next 07:00,
  service not run (matches RunAtLoad false)
- CodexUpdate: timer registered for 04:00; service deliberately left
  un-started (has global-install side effects) — `systemctl --user
  is-active` confirms `inactive`

Also fixes BackgroundServices.md's now-inaccurate "macOS-only" claim in
the Fresh Install section.

Unrelated pre-existing issue found during verification, not fixed here
(out of scope — a DerivedSync.ts bug, not a scheduling/installer one):
DeriveDenyHashes.ts fails with ENOENT because
~/.claude/skills/_LIFEOS/ doesn't exist on this host; would fail
identically under launchd.
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