diff --git a/agent/src/service/install.ts b/agent/src/service/install.ts
index adae58e..8563098 100644
--- a/agent/src/service/install.ts
+++ b/agent/src/service/install.ts
@@ -11,7 +11,7 @@
* - FIX M-host-2service: base-app env (BIND_HOST/ALLOWED_ORIGINS/PORT/…) is routed to the
* base-app unit ONLY; the agent unit (which supervises frpc) never carries it.
*/
-import { dirname } from 'node:path'
+import { dirname, join } from 'node:path'
import type { AgentConfig } from '../config/agentConfig.js'
import {
agentLabel,
@@ -225,9 +225,15 @@ export async function installService(
if (platform === 'launchd') {
const baseAppPath = launchdPlistPath(deps.homedir(), baseAppLabel())
- deps.writeFile(baseAppPath, buildLaunchdPlist(baseAppExec, baseAppEnvWithPath, baseAppLabel()))
+ deps.writeFile(
+ baseAppPath,
+ buildLaunchdPlist(baseAppExec, baseAppEnvWithPath, baseAppLabel(), join(cfg.stateDir, 'base-app.log')),
+ )
const agentPath = launchdPlistPath(deps.homedir(), agentLabel())
- deps.writeFile(agentPath, buildLaunchdPlist([nodePath, bin, 'run'], agentEnv, agentLabel()))
+ deps.writeFile(
+ agentPath,
+ buildLaunchdPlist([nodePath, bin, 'run'], agentEnv, agentLabel(), join(cfg.stateDir, 'agent.log')),
+ )
for (const path of [baseAppPath, agentPath]) {
const { cmd, args } = launchdLoadCommand(path)
await deps.runCommand(cmd, args)
diff --git a/agent/src/service/launchd.ts b/agent/src/service/launchd.ts
index b4d1e76..772ca4b 100644
--- a/agent/src/service/launchd.ts
+++ b/agent/src/service/launchd.ts
@@ -78,6 +78,7 @@ export function buildLaunchdPlist(
programArguments: readonly string[],
env: ServiceEnv = {},
label: string = AGENT_LABEL,
+ logPath?: string,
): string {
return [
'',
@@ -91,6 +92,16 @@ export function buildLaunchdPlist(
' ',
' KeepAlive',
' ',
+ // launchd's default has no log sink (unlike systemd's journald), so a crashing unit is silent.
+ // Route stdout+stderr to a file so `pair --install` failures are diagnosable out of the box.
+ ...(logPath !== undefined
+ ? [
+ ' StandardOutPath',
+ ` ${escapeXml(logPath)}`,
+ ' StandardErrorPath',
+ ` ${escapeXml(logPath)}`,
+ ]
+ : []),
...environmentVariablesBlock(env),
'',
'',