feat(connect): keep macOS hosts reachable after reboot - #4960
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
One finding: BootServiceCommandError.message now selects caller-visible guidance by substring-matching the free-form step attribute. Everything else in the touched Effect service code (inline Context.Service interfaces, make/layer exports, environment-acquired dependencies, Schema.TaggedErrorClass failures with preserved cause, namespaced effect/* imports) follows the conventions.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a5b4c1b. Configure here.
ApprovabilityVerdict: Needs human review This PR adds macOS LaunchAgent support, a new platform capability with launchd integration for service lifecycle management (install, uninstall, restart, rollback). The scope includes system-level integration and new runtime behavior for auto-starting services after reboot, warranting human review. You can customize Macroscope's approvability policy. Learn more. |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
apps/server/src/cloud/bootService.test.ts (1)
66-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for a darwin session with no user ID.
provideHostRefsalways suppliesHostProcessUserIdas 501.bootService.tstreats a null user ID on darwin as unsupported at Lines 331-335 and returns an unsupported status at Lines 730-736. No test exercises that branch.Make the user ID configurable, then assert
BootServiceUnsupportedErrorandsupported: falsefor darwin with a null user ID.As per coding guidelines: "Backend behavior changes must include focused tests for that behavior."
💚 Proposed parameter change
const provideHostRefs = ( home: string, platform: NodeJS.Platform = "linux", pathEnvironment = "/test/bin:/usr/bin:/bin", + userId: number | null = 501, ) => Effect.provide( Layer.mergeAll( Layer.succeed(HostProcessPlatform, platform), Layer.succeed(HostProcessEnvironment, { HOME: home, PATH: pathEnvironment }), - Layer.succeed(HostProcessUserId, 501), + Layer.succeed(HostProcessUserId, userId), ConfigProvider.layer(ConfigProvider.fromEnv({ env: { HOME: home } })), ), );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/server/src/cloud/bootService.test.ts` around lines 66 - 78, Update provideHostRefs to accept a configurable user ID, including null, while preserving 501 as the default for existing tests. Add focused coverage for a darwin session using a null user ID, asserting that the boot flow raises BootServiceUnsupportedError and returns supported: false.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/server/src/cli/service.ts`:
- Line 54: The service status hint in apps/server/src/cli/service.ts lines 54-54
should use the documented npx t3@latest service install invocation. Update the
uninstall command in docs/user/background-service.md lines 46-47 to use npx
t3@latest service uninstall, keeping service management commands consistent.
In `@apps/server/src/cloud/bootService.ts`:
- Around line 423-434: Update the disabled-state parsing in
readLaunchAgentDisabled and the macOS status case in
apps/server/src/cloud/bootService.ts: recognize both “=> true” and “=> disabled”
forms while preserving the existing service-label match. Update the fixture in
apps/server/src/cloud/bootService.test.ts:554-557 to cover both boolean
spellings.
In `@docs/architecture/server-updates.md`:
- Around line 35-40: Run the Markdown formatter using `vp check --fix` on the
updated table, ensuring the separator and body rows have normalized column
widths and consistent trailing padding. Preserve the table content and commit
the formatter-clean result.
In `@docs/user/background-service.md`:
- Around line 9-11: Update the reboot behavior wording in the background setup
documentation to state that T3 Code “starts again automatically after a reboot,”
replacing the ambiguous “returns automatically” phrasing while preserving the
surrounding platform-specific startup details.
---
Nitpick comments:
In `@apps/server/src/cloud/bootService.test.ts`:
- Around line 66-78: Update provideHostRefs to accept a configurable user ID,
including null, while preserving 501 as the default for existing tests. Add
focused coverage for a darwin session using a null user ID, asserting that the
boot flow raises BootServiceUnsupportedError and returns supported: false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8a005863-11da-44bd-af41-6af8dd2de975
📒 Files selected for processing (12)
apps/server/src/atomicWrite.tsapps/server/src/cli/connect.tsapps/server/src/cli/service.test.tsapps/server/src/cli/service.tsapps/server/src/cloud/bootService.test.tsapps/server/src/cloud/bootService.tsapps/server/src/cloud/selfUpdate.test.tsapps/server/src/cloud/selfUpdate.tsdocs/architecture/server-updates.mddocs/user/background-service.mdpackages/contracts/src/environment.tspackages/shared/src/hostProcess.ts
| return "T3 Code service\n Status: unavailable on this machine\n Supported on: macOS with launchd or Linux with systemd"; | ||
| } | ||
| if (!status.installed) { | ||
| return "T3 Code service\n Status: not installed\n Next: Run `t3 service install`."; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use one command invocation for service management.
The CLI status hint and documentation use different executables. Users who use the documented npx flow may not have a bare t3 command.
apps/server/src/cli/service.ts#L54-L54: usenpx t3@latest service install.docs/user/background-service.md#L46-L47: usenpx t3@latest service uninstall.
📍 Affects 2 files
apps/server/src/cli/service.ts#L54-L54(this comment)docs/user/background-service.md#L46-L47
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/cli/service.ts` at line 54, The service status hint in
apps/server/src/cli/service.ts lines 54-54 should use the documented npx
t3@latest service install invocation. Update the uninstall command in
docs/user/background-service.md lines 46-47 to use npx t3@latest service
uninstall, keeping service management commands consistent.
| Effect.map((result) => | ||
| result.code === 0 | ||
| ? Option.some( | ||
| result.stdout | ||
| .split("\n") | ||
| .some( | ||
| (line) => | ||
| line.includes(BOOT_SERVICE_LAUNCH_AGENT_LABEL) && line.includes("=> true"), | ||
| ), | ||
| ) | ||
| : Option.none<boolean>(), | ||
| ), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
launchctl print-disabled output format "=> disabled" macOS Sonoma
💡 Result:
On macOS Sonoma and other modern versions of macOS, the output format of the launchctl print-disabled command is not a formal API and is not guaranteed to remain stable across OS releases [1][2][3]. However, in practice, the output follows a structured key-value pattern for services within a specified domain [4]. The command outputs a block of text starting with a header such as disabled services = {, followed by a list of service labels mapped to a boolean value [4]. The format generally appears as: "service.label.name" => boolean In this mapping, the boolean value indicates the disabled state: - true: The service is disabled [4][5]. - false: The service is enabled [6][4]. Example output: disabled services = { "com.example.service1" => true "com.example.service2" => false } Because this output format is intended for human diagnostic use rather than programmatic parsing, Apple explicitly warns against relying on its structure in production scripts or automation [1][2][3]. Developers attempting to parse this output should be aware that it may change without warning and that relying on specific regex patterns may break after system updates [6]. If you need to check for specific disabled services, it is recommended to use the output for human inspection or to use more stable management subcommands when possible [7][8].
Citations:
- 1: https://leancrew.com/all-this/man/man1/launchctl.html
- 2: https://manp.gs/mac/1/launchctl
- 3: https://www.unix.com/man_page/mojave/1/launchctl/
- 4: https://rakhesh.com/mac/macos-launchctl-commands/
- 5: https://www.real-world-systems.com/docs/launchctl.1.html
- 6: fix(launchd): correct getDisabledMap regex for launchctl print-disabled output spectra-the-bot/openclaw-plugins#48
- 7: https://www.alansiu.net/2023/11/15/launchctl-new-subcommand-basics-for-macos/
- 8: https://www.alansiu.net/2025/05/28/using-new-launchctl-subcommands-to-check-for-and-reload-launch-daemons/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate files =="
git ls-files 'apps/server/src/cloud/bootService.ts' 'apps/server/src/cloud/bootService.test.ts' | sed -n '1,20p'
echo
echo "== relevant bootService snippets =="
sed -n '400,445p' apps/server/src/cloud/bootService.ts
echo
sed -n '1,80p' apps/server/src/cloud/bootService.ts
echo
echo "== relevant bootService.test snippet =="
sed -n '535,565p' apps/server/src/cloud/bootService.test.ts
echo
echo "== readLaunchAgentDisabled usages =="
rg -n "readLaunchAgentDisabled|launchctl print-disabled|print-disabled|rollbackFailedInstall|status" apps/server/src/cloud/bootService.ts apps/server/src/cloud/bootService.test.tsRepository: pingdotgg/t3code
Length of output: 9698
Make the disabled-state parser accept the possible launchctl print-disabled boolean forms. readLaunchAgentDisabled and the macOS status case only accept "service" => true; change the parser to also accept "service" => disabled, and update the fixture to cover both spellings so the parser is not tied to one observed output.
📍 Affects 2 files
apps/server/src/cloud/bootService.ts#L423-L434(this comment)apps/server/src/cloud/bootService.test.ts#L554-L557
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/cloud/bootService.ts` around lines 423 - 434, Update the
disabled-state parsing in readLaunchAgentDisabled and the macOS status case in
apps/server/src/cloud/bootService.ts: recognize both “=> true” and “=> disabled”
forms while preserving the existing service-label match. Update the fixture in
apps/server/src/cloud/bootService.test.ts:554-557 to cover both boolean
spellings.
| | Advertised value | Process shape | Client behavior | | ||
| | ----------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | ||
| | `boot-service` | Server running under the T3-managed systemd user service or macOS LaunchAgent | Call the update RPC; the managed service switches runtime and restarts. | | ||
| | `respawn` | Published npm CLI running in the foreground on macOS or Linux | Call the update RPC; the process hands off to a detached replacement. | | ||
| | `desktop-managed` | Backend supervised by the desktop app | Tell the user to update the desktop app on the server machine. | | ||
| | absent | Older server, development checkout, Windows foreground process, or an unrecognized supervisor | Offer the exact manual relaunch command. | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Run the Markdown formatter on the updated table.
The separator row at Line 36 is wider than the header row at Line 35, and the body rows use inconsistent trailing padding. The table still renders, but the column widths are not normalized.
Run vp check --fix and commit the result.
As per coding guidelines: "Markdown edits must be formatter-clean; run vp check --fix before committing."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/architecture/server-updates.md` around lines 35 - 40, Run the Markdown
formatter using `vp check --fix` on the updated table, ensuring the separator
and body rows have normalized column widths and consistent trailing padding.
Preserve the table content and commit the formatter-clean result.
Source: Coding guidelines
| After T3 Connect links the machine, accept the default background setup. T3 Code starts immediately | ||
| and returns automatically after a reboot. On macOS, the per-user LaunchAgent starts after you sign | ||
| in. On Linux, the systemd user service starts at boot and keeps running after you log out. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Clarify the reboot behavior.
“Returns automatically after a reboot” does not clearly state that the service starts again. Replace it with “starts again automatically after a reboot.”
Proposed wording
- and returns automatically after a reboot.
+ and starts again automatically after a reboot.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| After T3 Connect links the machine, accept the default background setup. T3 Code starts immediately | |
| and returns automatically after a reboot. On macOS, the per-user LaunchAgent starts after you sign | |
| in. On Linux, the systemd user service starts at boot and keeps running after you log out. | |
| After T3 Connect links the machine, accept the default background setup. T3 Code starts immediately | |
| and starts again automatically after a reboot. On macOS, the per-user LaunchAgent starts after you sign | |
| in. On Linux, the systemd user service starts at boot and keeps running after you log out. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/user/background-service.md` around lines 9 - 11, Update the reboot
behavior wording in the background setup documentation to state that T3 Code
“starts again automatically after a reboot,” replacing the ambiguous “returns
automatically” phrasing while preserving the surrounding platform-specific
startup details.
|
Note 🤖 Fable 5 responding on behalf of Theo Closing: #6286 shipped the macOS LaunchAgent and this branch cannot be rebased onto it. Verified, not judgment: both PRs rewrite the same Judgment: #6286 is also the better shape. No platform branches in the lifecycle flow, PATH handled at server startup by |

Bare
npx t3 connectcould link a Mac, but it could not leave behind a durable server. The existing background-service path only supported Linux, so the connection disappeared when the CLI exited and did not return after login or reboot.This adds a per-user macOS LaunchAgent to the existing service lifecycle. Connect pins the exact running T3 version, starts it through a stable runtime path, and launchd keeps it alive and loads it again after login. Remote updates atomically switch that stable path and restart the same LaunchAgent, with rollback if launchd rejects the restart. Linux systemd behavior is unchanged.
The LaunchAgent preserves the setup PATH for provider discovery, reports the macOS background-item setting when activation is blocked, and is covered by focused lifecycle, rollback, capability, updater, CLI, and documentation tests.
Verification:
vp test run apps/server/src/cloud/bootService.test.ts apps/server/src/cloud/selfUpdate.test.ts apps/server/src/cli/service.test.ts apps/server/src/cli/connect.test.ts apps/server/src/bin.test.tsvp run --filter @t3tools/shared --filter @t3tools/contracts --filter t3 typecheckvp linton changed TypeScript filesGenerated by GPT-5.6-sol using the Codex harness in T3 Code.
Note
Medium Risk
Changes host lifecycle (launchctl, symlinks, rollback) and remote self-update on macOS; Linux paths are largely preserved but shared install/status code grew. Mistakes could leave a broken background service or failed rollback.
Overview
Adds macOS launchd alongside Linux systemd so T3 Code can stay reachable after login and reboot, not only on Linux.
Boot service:
BootServiceinstalls a per-user LaunchAgent (com.t3tools.t3code.server), pins the CLI into a managed runtime, and runsservethrough a stable symlink atruntime/service/currentso remote updates can swap versions without rewriting the plist. Install/update useslaunchctl(enable, bootstrap, bootout) with rollback on failure; status checks plist, symlink, loaded state, and Login Items disablement. macOS always pins the exact CLI version (Linux can still reuse stable global/checkout entries).writeSymbolicLinkAtomicallymirrors atomic file writes for symlink switches.Self-update & connect: macOS LaunchAgent processes advertise
boot-service; updates switch the runtime symlink andkickstart -k, with rollback if launchd rejects the restart.HostProcessUserIdsupplies the GUI domain for launchctl. CLI/docs messaging now covers macOS + Linux and softer “start automatically in the background” wording.Reviewed by Cursor Bugbot for commit b3a3007. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add macOS launchd support to keep hosts reachable after reboot
bootService.ts: writes a plist, manages an active runtime symlink atomically, and starts/stops/uninstalls the service vialaunchctl.launchctl kickstart; on failure it restores the previous symlink target.writeSymbolicLinkAtomicallyinatomicWrite.tsandHostProcessUserIdinhostProcess.tsto support atomic link management and per-user launchd targets.launchctlsteps; partial failures trigger a rollback that restores the previous plist, symlink, and loaded state.Macroscope summarized b3a3007.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation