Linux compatibility
Status — updated 2026-07-11: ✅ Phase 1 (the 🔴 service-layer blocker) is DONE. A Linux systemd --user backend, sitting behind a platform-neutral ServiceManager seam, shipped in #515 (CI all-green, pending merge). Remaining work — this issue stays open until it lands: the four "works-but-untested" Linux branches still have zero Linux-path test coverage (🟡 Phase 2), and CI still runs only on ubuntu-latest (no macOS matrix). Original scoping below is preserved as filed; see the status comment for the up-to-date breakdown.
Text clipboard, keychain, OAuth, and terminal-spawn already carry working Linux branches (unchanged) — those were never the gap.
✅ Blocker RESOLVED in #515 — Service layer is now cross-platform (systemd --user backend)
Done (PR #515): extracted a platform-neutral ServiceManager interface + serviceManagerFor(platform) factory (src/service/{types,index}.ts), added a full systemd --user backend (src/service/systemd/*) with a companion .path unit for the WatchPaths equivalent, adapted launchd behind the same interface (776-line launchd suite untouched), replaced the assertMacOS() hard-throw with factory dispatch, and updated the service-setup/get-started skills. tsc --noEmit clean; full vitest 11,233 passed / 0 failed; CI all-green. Original problem statement preserved below.
afk service (install/uninstall/status as an always-on, auto-restart background process) hard-refuses on any non-darwin platform and there is no systemd implementation shipped.
src/cli/commands/service.ts:37-43 — assertMacOS() throws 'afk service' uses macOS launchd and is only supported on darwin. It gates the install/uninstall actions (called at service.ts:65).
src/service/ contains only launchd.ts + launchd.test.ts — no systemd module. Confirmed: zero matches for systemd or WantedBy anywhere under src/.
- The service-setup skill still tells Linux users the feature is unavailable:
src/skills/service-setup/prompts/system.md:35 — "On Linux you'd want a systemd user unit (not yet shipped)."
Work item (headline remaining work): add a systemd user-unit backend (~/.config/systemd/user/*.service + systemctl --user bootstrap), branch service.ts on process.platform, and replace the assertMacOS() hard-throw with a platform dispatch. daemon and telegram are the two service names to support (SERVICE_NAMES, service.ts:26).
🟡 Linux paths that WORK but are UNTESTED
Each branch below was code-read and confirmed correct. The blocker is coverage: no test exercises the Linux branch of any of these — verified by reading each corresponding *.test.ts.
🟡 CI — add a macOS matrix entry
Acceptance criteria
Out of scope
- Windows support (no
win32 branches audited or planned here).
- Native Keychain/libsecret integration — the Linux OAuth path deliberately uses a
0o600 plaintext file at ~/.claude/.credentials.json (oauth.ts:133-137), matching Claude Code's on-disk format; a hardened secret-store backend is a separate follow-up.
- Replacing the macOS
security-CLI keychain shell-out with a native binding (already a follow-up in oauth.ts:126-127).
- Bundling/pinning a Chrome binary — detection remains best-effort with a soft note, never a hard failure.
Linux compatibility
Status — updated 2026-07-11: ✅ Phase 1 (the 🔴 service-layer blocker) is DONE. A Linux systemd
--userbackend, sitting behind a platform-neutralServiceManagerseam, shipped in #515 (CI all-green, pending merge). Remaining work — this issue stays open until it lands: the four "works-but-untested" Linux branches still have zero Linux-path test coverage (🟡 Phase 2), and CI still runs only onubuntu-latest(no macOS matrix). Original scoping below is preserved as filed; see the status comment for the up-to-date breakdown.Text clipboard, keychain, OAuth, and terminal-spawn already carry working Linux branches (unchanged) — those were never the gap.
✅ Blocker RESOLVED in #515 — Service layer is now cross-platform (systemd
--userbackend)afk service(install/uninstall/status as an always-on, auto-restart background process) hard-refuses on any non-darwin platform and there is no systemd implementation shipped.src/cli/commands/service.ts:37-43—assertMacOS()throws'afk service' uses macOS launchd and is only supported on darwin.It gates theinstall/uninstallactions (called atservice.ts:65).src/service/contains onlylaunchd.ts+launchd.test.ts— no systemd module. Confirmed: zero matches forsystemdorWantedByanywhere undersrc/.src/skills/service-setup/prompts/system.md:35— "On Linux you'd want a systemd user unit (not yet shipped)."Work item (headline remaining work): add a systemd user-unit backend (
~/.config/systemd/user/*.service+systemctl --userbootstrap), branchservice.tsonprocess.platform, and replace theassertMacOS()hard-throw with a platform dispatch.daemonandtelegramare the two service names to support (SERVICE_NAMES,service.ts:26).🟡 Linux paths that WORK but are UNTESTED
Each branch below was code-read and confirmed correct. The blocker is coverage: no test exercises the Linux branch of any of these — verified by reading each corresponding
*.test.ts.readProcessMeta()/procparsing —src/telegram/manager.ts:276-293reads/proc/<pid>/stat(starttime) +/proc/<pid>/status(VmRSS) on Linux. ❌ Not covered —manager.test.tshas noreadProcessMeta//procreference. Add a Linux test that mocksreadFileSync/statSyncfor those paths and asserts{ uptimeSec, memoryMb }.defaultBackend()Linux credential file —src/agent/mcp/oauth.ts:106-137reads/writes~/.claude/.credentials.json(mode0o600) on Linux. ❌ Not covered —oauth.test.tshas nodefaultBackend/credentials.jsonreference. Add a Linux test that stubsprocess.platform, pointshomedir()at a temp dir, and round-trips read/write (asserting0o600).src/cli/commands/browser.ts:140-164probesgoogle-chrome/google-chrome-stable/chromium/chromium-browseron non-darwin. ❌ Not covered and currently untestable as written —detectChromeMajorVersionis private/non-exported. Export it (or inject anexecFileSyncseam), then add a Linux test asserting the candidate list is probed and a version parsed.src/agent/tools/handlers/terminal-font-size.ts:72-77appends~/.config/Code/User/settings.json+~/.config/Cursor/User/settings.jsonon Linux. ❌ Not covered — every test interminal-font-size.test.tsinjects a mockdiscoverFn, so the realdiscoverEditors()Linux branch is never invoked. Add a test that calls the realdiscoverEditors()withprocess.platformstubbed tolinuxandHOMEat a temp dir.🟡 CI — add a macOS matrix entry
ubuntu-latestonly —.github/workflows/ci.yml:25/75/113/145, pluspublish.ymlandauto-release.yml. Nomacos-latest/matrix.osanywhere in.github/workflows/. Convert thetestjob (ci.yml:73-105) tostrategy.matrix.os: [ubuntu-latest, macos-latest]withruns-on: ${{ matrix.os }}, so the darwinps -p/security//Applications/...branches get exercised alongside the Linux branches. The fork-PR constraint (GitHub-hosted runners only) is already satisfied —macos-latestis GitHub-hosted.Acceptance criteria
afk service install {daemon,telegram}works on Linux via a systemd user unit, andassertMacOS()'s hard-throw is replaced by a platform dispatch (service.ts:37-43,:65). — feat(service): Linux systemd --user support behind a platform-neutral ServiceManager #515afk service {status,uninstall}operate against the systemd unit on Linux. — feat(service): Linux systemd --user support behind a platform-neutral ServiceManager #515system.md:35). — feat(service): Linux systemd --user support behind a platform-neutral ServiceManager #515readProcessMeta()has a Linux/proc-path test (manager.ts:276-293).defaultBackend()has a Linux.credentials.jsonread+write test (oauth.ts:106-137).browser.ts:140-164).discoverEditors()has a Linux-branch test invoking the real discovery (terminal-font-size.ts:72-77).ubuntu-latestandmacos-latestviamatrix.os(ci.yml:73-105).Out of scope
win32branches audited or planned here).0o600plaintext file at~/.claude/.credentials.json(oauth.ts:133-137), matching Claude Code's on-disk format; a hardened secret-store backend is a separate follow-up.security-CLI keychain shell-out with a native binding (already a follow-up inoauth.ts:126-127).