Fix/linux support#1126
Conversation
Pulse and several PAI tools import grammy, jose, minisearch, smol-toml, yaml, openai, and @anthropic-ai/claude-agent-sdk at runtime. The v5.0.0 release ships without a top-level package.json, so a fresh extraction fails immediately on: $ bun run pulse.ts error: Cannot find package 'smol-toml' from '/home/danielm/.claude/PAI/PULSE/pulse.ts' The release .gitignore intentionally ignores /package.json to block accidental Claude-Code debris. Adds an explicit negation for the deliberate runtime manifest. After this change a fresh install runs: cd ~/.claude && bun install and Pulse starts cleanly. Affects every platform — not Linux-specific.
Three .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc files shipped as symlinks to ../../CLAUDE.md. They're Cursor IDE artifacts (.mdc with description/globs/alwaysApply frontmatter) — Claude Code reads CLAUDE.md, not .cursor/rules/*.mdc. Nothing in PAI references these files (only docs in skills/Migrate/SKILL.md mention Cursor as a migration source). These also crashed Bun's fs watcher on Linux because the symlinks extracted as absolute self-references after tarball + rsync, producing ELOOP and exiting Pulse. Deleting them removes both the dead-weight artifact and the failure mode in one cut. Removed: PAI/PULSE/Observability/.cursor/ skills/Art/Tools/.cursor/ skills/Prompting/Templates/Tools/.cursor/
Bun.spawn(["bash", "-c", ...]) relies on PATH for command resolution.
On Linux, when Pulse runs under a minimal-env service manager (or even
launched headless from some shells) PATH can be too sparse for
posix_spawn to find bash, producing:
assistant-tasks failed: ENOENT no such file or directory, posix_spawn 'bash'
Resolve bash via Bun.which("bash") at module load and fall back to
/bin/bash — present on macOS natively and on every mainstream Linux
distro. macOS behavior unchanged because /bin/bash is the same path it
already used implicitly.
Linux companion to com.pai.pulse.plist. Mirrors the plist's behavior: - same WorkingDirectory and ExecStart pattern - same __HOME__ / __BUN_PATH__ placeholders for manage.sh substitution - same stdout/stderr log paths - Restart=always with RestartSec=30 (mirrors KeepAlive + ThrottleInterval=30) Standalone — manage.sh wiring lands in the next commit so this can be verified in isolation. Manual install instructions are at the top of the file for users who want to enable Pulse on Linux today.
Pre-existing manage.sh hard-coded launchctl + ~/Library/LaunchAgents
which makes Pulse undeployable on Linux: the script silently no-ops
launchctl, the plist becomes inert, and the daemon never starts on
boot/login.
Refactor:
- extract per-OS lifecycle into service_start / service_stop /
service_install / service_uninstall helpers
- Darwin path is byte-for-byte equivalent to the prior behavior
- Linux path renders pai-pulse.service from the v5 template,
drops it under ~/.config/systemd/user, runs daemon-reload,
and enable --now so Pulse starts immediately
- install verification (curl :31337 loop) preserved; on Linux
failure, the error hint also surfaces 'journalctl --user -u
pai-pulse' so users find the right log
- prints a hint to enable-linger so the service survives logout
- rejects unsupported uname with a clear error
The release ships the directory as PAI/PULSE/ (uppercase) but 11 path joins across 9 .ts files use "Pulse" (TitleCase). On macOS this is masked by the case-insensitive default filesystem (HFS+/APFS). On Linux ext4 (case-sensitive), the join produces a path that does not exist: GET / → HTTP 404 # observability dashboard 404s entirely STATE_DIR / state.json reads fail cron jobs that cd into PULSE_DIR fall back to wrong cwd Aligns every path join with the on-disk directory name (PULSE). macOS behavior is unchanged because the case-insensitive FS treats both spellings as the same inode. Files updated: PAI/PULSE/lib.ts (1 ref) PAI/PULSE/pulse-old.ts (1 ref) PAI/PULSE/pulse-unified.ts (1 ref) PAI/PULSE/run-job.ts (1 ref) PAI/PULSE/setup.ts (1 ref) PAI/PULSE/Observability/observability.ts (3 refs) PAI/PULSE/Performance/cost-aggregator.ts (1 ref) PAI/PULSE/checks/github-work.ts (1 ref) PAI/PULSE/checks/notification-governor.ts (1 ref) PAI/PULSE/checks/poller-meta-monitor.ts (2 refs) PAI/PULSE/modules/imessage.ts (2 refs) PAI/PULSE/modules/user-index.ts (1 ref)
Three small Linux-support warts the prior commits missed: - engine/actions.ts: installPulse() comment block now describes both launchd (macOS) and systemd (Linux) paths instead of launchd-only. - engine/actions.ts: wizard prompt label changed from "Install Pulse as a system launchd service?" to "Install Pulse as a system service?" so Linux users see neutral terminology. - engine/validate.ts: the post-install "auto-start on login" check now branches on platform() — checks ~/Library/LaunchAgents/ com.pai.pulse.plist on macOS and ~/.config/systemd/user/ pai-pulse.service on Linux. Previously hardcoded the macOS path, producing a false-negative "Pulse launchd agent: Not installed" on healthy Linux installs. Verified on Arch Linux: validate now reports "Pulse systemd unit: Installed at ~/.config/systemd/user/pai-pulse.service".
|
Hey @dmcgoldrd, thanks for raising this, and sorry it sat for a while. We're changing how LifeOS ships. Instead of cloning a full That's aimed right at what you hit here. The old "one directory, one layout, hope it matches your setup" approach is exactly what broke for so many people, and the new model should handle it far better because your AI does the integration per machine instead of us guessing. So we're closing this in prep for that release. If it still bites you once the skill-based version is out, reopen or file a fresh one and we'll jump on it. Appreciate you taking the time. |
What this is
A bug fix PR — closes the gap that prevented
Releases/v5.0.0/.claude/from installing end-to-end on Linux. macOS path is unchanged.Every edit is scoped to
Releases/v5.0.0/.claude/— no impact on v4.x or anywhere else in the tree.What it changes (7 commits)
package.jsonat.claude/root sobun installresolves Pulse runtime deps on a fresh extract.bashabsolutely inPULSE/lib.ts spawnScript()so subprocesses don't depend on PATH ordering.PULSE/pai-pulse.service— systemd user-unit template, companion to the existingcom.pai.pulse.plist. Same__HOME__/__BUN_PATH__placeholder pattern.PULSE/manage.shOS-aware —start/stop/install/uninstallroute to launchd on macOS,systemctl --useron Linux. Install kills any stalebun.*pulse.ts, renders the unit, runsdaemon-reload+enable --now, and verifies:31337binds within 10s — failing loud if not.Pulse/→PULSE/across 9 TypeScript files. macOS didn't care, Linux did..cursor/rules/*.mdcIDE artifacts from the public release.engine/validate.tsOS-aware — neutral "system service" wording instead of "launchd"; post-install validator branches onplatform()and checks the correct artifact (launchd plist on macOS, systemd unit on Linux) so Linux installs no longer false-fail withPulse launchd agent: Not installed.Tested on a fresh-ish system
Arch Linux x86_64, kernel 6.19.x, bun 1.3.6 via mise, fresh
~/.claude/with prior install backed up to~/.claude.backup-<timestamp>/.1.
bash ~/.claude/PAI/PULSE/manage.sh install— the OS-aware install path the wizard calls:2. Post-install probes:
Any stale pre-install pulse process from the backup tree was correctly killed by the install's
pkill -9 -f "bun.*pulse.ts"step.3. Validate.ts spot-check (the
platform()branch from the last commit):Confirms the post-install validator now passes on Linux instead of false-failing on a missing launchd plist.
4. Typecheck:
bun tsc --noEmit -p PAI-Install/tsconfig.json— no errors inengine/actions.tsorengine/validate.ts.Before / after example
Linux user runs
curl -sSL https://ourpai.ai/install.sh | bash, picks "Yes" at the Pulse install prompt, then runs the validator:~/.config/systemd/user/pai-pulse.servicerenderedsystemctl --user is-activeinactive(no unit)active:31337bun(PID matchesstate/pulse.pid)Pulse launchd agent: Not installed(false-negative on Linux)Pulse systemd unit: Installed at ~/.config/systemd/user/pai-pulse.servicecurl POST /notifymacOS impact
None. All OS branches default to the macOS path;
manage.shandvalidate.tsroute viacase "$OS"/platform() === "linux"so existing macOS behavior is byte-identical.Known follow-ups (intentionally not in this PR)
engine/actions.ts:1859descriptive emit message still says "Installing it as a launchd agent makes it auto-start..." — clean cosmetic follow-up to neutralize.engine/actions.ts:1874+) is structurally macOS-only. No cross-platform menu bar work in this PR.