fix: track effective error detail across retries, detect 'Could not find domain'
Address review feedback: - Use effectiveDetail to propagate the most relevant error message - Only show GUI-session guidance when failure is actually gui-domain related - Add 'could not find domain for' to isUnsupportedGuiDomain() detection - New test for the exact error string from issue #8619
This commit is contained in:
parent
1684577259
commit
e57ebd641c
@ -497,6 +497,19 @@ describe("launchd install", () => {
|
||||
expect(bootoutAfterBootstrap).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it("falls back to launchctl load for 'Could not find domain' error from issue #8619", async () => {
|
||||
state.bootstrapError = "Could not find domain for user gui: 1000";
|
||||
// loadError is empty → launchctl load succeeds
|
||||
const env = createDefaultLaunchdEnv();
|
||||
const stdout = new PassThrough();
|
||||
await installLaunchAgent({
|
||||
env,
|
||||
stdout,
|
||||
programArguments: defaultProgramArguments,
|
||||
});
|
||||
expect(state.launchctlCalls.some((c) => c[0] === "load" && c[1] === "-w")).toBe(true);
|
||||
});
|
||||
|
||||
it("surfaces generic bootstrap failures without GUI-specific guidance", async () => {
|
||||
state.bootstrapError = "Operation not permitted";
|
||||
const env = createDefaultLaunchdEnv();
|
||||
|
||||
@ -201,6 +201,10 @@ async function bootstrapLaunchAgentOrThrow(params: {
|
||||
return;
|
||||
}
|
||||
const detail = (boot.stderr || boot.stdout).trim();
|
||||
// Track the most relevant error detail across retries so that the final
|
||||
// error message shown to the user reflects the actual failure, not a stale
|
||||
// "already bootstrapped" message from an earlier attempt.
|
||||
let effectiveDetail = detail;
|
||||
|
||||
// If the service is already registered (e.g. re-install without prior uninstall),
|
||||
// bootout the stale registration and retry once.
|
||||
@ -211,6 +215,7 @@ async function bootstrapLaunchAgentOrThrow(params: {
|
||||
return;
|
||||
}
|
||||
const retryDetail = (retry.stderr || retry.stdout).trim();
|
||||
effectiveDetail = retryDetail;
|
||||
if (isUnsupportedGuiDomain(retryDetail)) {
|
||||
// Fall through to the legacy load fallback below.
|
||||
} else if (!isAlreadyBootstrapped(retryDetail)) {
|
||||
@ -220,18 +225,22 @@ async function bootstrapLaunchAgentOrThrow(params: {
|
||||
|
||||
// If the gui/ domain is unavailable (SSH, headless, or sudo), try the
|
||||
// deprecated-but-universal `launchctl load` as a last resort before giving up.
|
||||
if (isUnsupportedGuiDomain(detail) || isAlreadyBootstrapped(detail)) {
|
||||
if (isUnsupportedGuiDomain(effectiveDetail) || isAlreadyBootstrapped(effectiveDetail)) {
|
||||
const load = await execLaunchctl(["load", "-w", params.plistPath]);
|
||||
if (load.code === 0) {
|
||||
return;
|
||||
}
|
||||
// `launchctl load` also failed — throw the original gui-domain error so the
|
||||
// user gets the actionable hint about desktop sessions.
|
||||
throwBootstrapGuiSessionError({
|
||||
detail,
|
||||
domain: params.domain,
|
||||
actionHint: params.actionHint,
|
||||
});
|
||||
const loadDetail = (load.stderr || load.stdout).trim();
|
||||
// Only show GUI-session guidance when the failure is actually gui-domain related.
|
||||
// For other load failures (e.g. plist syntax, permissions), surface the real error.
|
||||
if (isUnsupportedGuiDomain(effectiveDetail)) {
|
||||
throwBootstrapGuiSessionError({
|
||||
detail: effectiveDetail,
|
||||
domain: params.domain,
|
||||
actionHint: params.actionHint,
|
||||
});
|
||||
}
|
||||
throw new Error(`launchctl load failed: ${loadDetail || effectiveDetail}`);
|
||||
}
|
||||
throw new Error(`launchctl bootstrap failed: ${detail}`);
|
||||
}
|
||||
@ -469,7 +478,8 @@ function isUnsupportedGuiDomain(detail: string): boolean {
|
||||
const normalized = detail.toLowerCase();
|
||||
return (
|
||||
normalized.includes("domain does not support specified action") ||
|
||||
normalized.includes("bootstrap failed: 125")
|
||||
normalized.includes("bootstrap failed: 125") ||
|
||||
normalized.includes("could not find domain for")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user