fix(daemon): enable LaunchAgent before bootstrap on restart#39237
fix(daemon): enable LaunchAgent before bootstrap on restart#39237steipete merged 2 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR correctly fixes a latent restart failure in the macOS LaunchAgent lifecycle by adding What's fixed:
Remaining gap: Confidence Score: 3/5
|
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🔵 Unvalidated OPENCLAW_LAUNCHD_LABEL allows arbitrary launchctl service enable and plist path traversal during restart/repair
Description
Security impact if the caller’s environment is attacker-influenced (e.g., higher-privileged automation invoking this code while inheriting untrusted env):
Vulnerable code (new behavior): const label = resolveLaunchAgentLabel({ env });
const plistPath = resolveLaunchAgentPlistPath(env);
await execLaunchctl(["enable", `${domain}/${label}`]);
const boot = await execLaunchctl(["bootstrap", domain, plistPath]);Label source + plist path construction: const envLabel = args?.env?.OPENCLAW_LAUNCHD_LABEL?.trim();
if (envLabel) return envLabel;
...
return path.posix.join(home, "Library", "LaunchAgents", `${label}.plist`);RecommendationTreat
function assertSafeLaunchdLabel(label: string): string {
// Reverse-DNS style, no slashes, no traversal, reasonable charset.
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,200}$/.test(label)) {
throw new Error(`Invalid launchd label: ${label}`);
}
if (label.includes("/") || label.includes("\\") || label.includes("..")) {
throw new Error(`Invalid launchd label: ${label}`);
}
return label;
}
const label = assertSafeLaunchdLabel(resolveLaunchAgentLabel({ env }));If overrides are not required in production, remove
const launchAgentsDir = path.posix.join(home, "Library", "LaunchAgents");
const plistPath = path.posix.join(launchAgentsDir, `${label}.plist`);
const resolved = path.posix.normalize(plistPath);
if (!resolved.startsWith(`${launchAgentsDir}/`)) {
throw new Error("Resolved plist path escapes LaunchAgents directory");
}
Analyzed PR: #39237 at commit Last updated on: 2026-03-09T02:26:04Z |
|
Addressed the gap flagged by @greptile-apps — pushed a second commit that adds the same All three LaunchAgent lifecycle paths ( Updated test verifies the |
|
Perfect! The fix looks good. All three LaunchAgent paths now have the same defensive
The comments match, and the tests correctly verify the ordering. This completely closes the gap — no more silent bootstrap failures from persisted "disabled" state. |
restartLaunchAgent was missing the launchctl enable call that installLaunchAgent already performs. launchd can persist a "disabled" state after bootout, causing bootstrap to silently fail and leaving the gateway unloaded until a manual reinstall. Fixes openclaw#39211 Co-Authored-By: Claude Opus 4.6 <[email protected]>
The repair/recovery path had the same missing `enable` guard as `restartLaunchAgent`. If launchd persists a "disabled" state after a previous `bootout`, the `bootstrap` call in `repairLaunchAgentBootstrap` fails silently, leaving the gateway unloaded in the recovery flow. Add the same `enable` guard before `bootstrap` that was already applied to `installLaunchAgent` and (in this PR) `restartLaunchAgent`. Co-Authored-By: Claude Opus 4.6 <[email protected]>
d2328ab to
5334675
Compare
|
Landed via temp rebase onto main. Thanks @scoootscooob! |
Summary
launchctl enablecall beforebootstrapinrestartLaunchAgent(), matching the existing guard ininstallLaunchAgent()bootout, causingbootstrapto fail and leaving the gateway unloaded until manual reinstallRoot cause
installLaunchAgent(line 414) already callsenablebeforebootstrapwith the comment:But
restartLaunchAgentwas missing this step, leaving a window where bootstrap silently fails after bootout.Test plan
bootout → enable → bootstrap → kickstartorderFixes #39211
🤖 Generated with Claude Code