Skip to content

Run the LastCode server headlessly on macOS via launchd #39

Description

@lastobelus

Problem

On macOS, the full Electron LastCode client currently owns the server process. A machine used primarily as a remote LastCode environment must therefore keep the renderer and GPU processes open even when no local UI is needed. That overhead is material on older Intel hardware.

The goal is to keep the existing LastCode home, environment identity, pairing, projects, and configured private remote endpoint available without running the graphical client.

Current state

  • LastCode already ships a cross-platform headless serve command.
  • t3 service install|update|status|uninstall currently supports Linux/systemd only and fails closed on Darwin.
  • The existing pinned-service runtime hardcodes npm package t3@<version> and node_modules/t3/dist/bin.mjs. Porting it unchanged would risk running upstream T3 Code instead of the installed LastCode build.
  • A macOS LaunchAgent is login-scoped: it starts after user login and stops at logout. It is not equivalent to a boot daemon or Linux user lingering.

Upstream prior art

  • Issue #659 contains an early working LaunchAgent wrapper with RunAtLoad, KeepAlive, logs, and lifecycle commands. It is closed as not planned and uses older launchctl load/unload, a source checkout, and embedded environment/credentials, so it should not be copied directly.
  • PR #4286 merged the current stable launcher, pinned runtime, service lifecycle, remote-update handoff, and rollback framework.
  • Issue #4913 records provider discovery failures when the service environment lacks the user's PATH. Current login-shell hydration may address that class, but mise-managed provider executables still need live launchd validation.
  • PR #4960 is an older, broad macOS service implementation. It remains open and dirty with unresolved review findings.
  • PR #6286 is the strongest current implementation: a smaller data-driven systemd/launchd manager, per-user LaunchAgent, RunAtLoad, KeepAlive, controlled shutdown, logs, and reuse of the stable launcher/update protocol. Its current checks are green and merge state is clean.
  • PR feat(server): run the background service on macOS via launchd pingdotgg/t3code#6286 is not safe to import unchanged. Its broad optional handling of launchctl bootout can hide a genuine unload failure and report success while the previous job remains active (review finding). It was validated on Apple Silicon, not Intel.
  • Discussion #6811 describes the intended graphical-client/headless-server boundary and the need to prevent competing ownership of one environment.
  • Discussion #6888 proposes launching the full desktop app at login, which does not remove Electron renderer/GPU overhead.

Recommendation

Deliver this in two phases.

Phase 1: operational bridge

Install a reversible user LaunchAgent that directly starts the packaged LastCode server against the existing LastCode home.

Use modern launchctl bootstrap/bootout, a service label distinct from the desktop bundle ID, RunAtLoad, KeepAlive, explicit logs, and loopback binding for the existing private remote mapping.

Do not use the native pinned-runtime installer yet: it currently resolves upstream t3, not a LastCode artifact.

Phase 2: native LastCode support

Add Darwin/launchd support to LastCode's service command using upstream PR pingdotgg#6286 as design evidence.

Before adopting it:

  • classify expected “not loaded” launchctl outcomes narrowly and fail on genuine unload/bootstrap errors;
  • make the pinned runtime source product-aware so LastCode never installs or starts upstream T3 Code;
  • validate macOS Intel as well as Apple Silicon;
  • preserve existing LastCode state, pairing, and remote-update protocol; and
  • migrate and remove the temporary LaunchAgent without allowing two servers to own one LastCode home.

Acceptance criteria

Phase 1

  • Quitting LastCode.app does not take the remote environment offline.
  • No Electron renderer or GPU process remains; only the headless server and active provider processes run.
  • The service uses the existing LastCode state, environment identity, pairing, and projects.
  • It binds to loopback and remains reachable through the configured private remote endpoint.
  • RunAtLoad starts it after login and KeepAlive restarts it after an unexpected exit.
  • Install, status, restart, stop, and uninstall operations are documented and reversible.
  • Logs have a stable path and contain actionable startup failures without credentials.
  • Provider discovery and one real read-only thread succeed under launchd with a mise-managed toolchain.
  • LaunchAgent limitations are explicit: no availability before GUI login and no survival after logout.
  • Starting the graphical app cannot silently create a second backend against the same LastCode database.

Phase 2

  • service install|update|status|uninstall supports Darwin through a first-class launchd manager.
  • The installed service is verifiably LastCode at the selected LastCode version and never fetches or starts upstream t3.
  • Known “not loaded” launchctl states are idempotent; all other lifecycle errors fail visibly.
  • Install/update stages immutable runtime artifacts before stopping the current service.
  • Failed activation restores the prior working service and database state.
  • Existing pairing and private remote access survive service updates.
  • Focused lifecycle tests cover install, repair, status, update, rollback, uninstall, disabled Login Item, logout semantics, and paths containing spaces/XML-sensitive characters.
  • End-to-end acceptance passes on Intel and Apple Silicon macOS.
  • Migration removes the Phase 1 job before native service activation and proves there is only one state owner.
  • User documentation covers logs, Login Items, TCC/privacy attribution, sleep behavior, and login-scoped availability.

Out of scope

  • Running the full Electron desktop client merely to keep the server alive.
  • Changing the deployment's network exposure.
  • Removing an existing recovery channel before the headless service and update rollback have survived real use.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions