daemon: prefer symlinked paths over realpath for stable service configs (#1505)

When installing the LaunchAgent/systemd service, the CLI was using
fs.realpath() to resolve the entry.js path, which converted stable
symlinked paths (e.g. node_modules/clawdbot) into version-specific
paths (e.g. .pnpm/clawdbot@X.Y.Z/...).

This caused the service to break after pnpm updates because the old
versioned path no longer exists, even though the symlink still works.

Now we prefer the original (symlinked) path when it's valid, keeping
service configs stable across package version updates.
This commit is contained in:
George Zhang
2026-01-23 19:52:26 +08:00
committed by GitHub
parent 2c85b1b409
commit bfbeea0f20
2 changed files with 34 additions and 0 deletions

View File

@@ -45,6 +45,26 @@ describe("resolveGatewayProgramArguments", () => {
]);
});
it("prefers symlinked path over realpath for stable service config", async () => {
// Simulates pnpm global install where node_modules/clawdbot is a symlink
// to .pnpm/clawdbot@X.Y.Z/node_modules/clawdbot
const symlinkPath = path.resolve(
"/Users/test/Library/pnpm/global/5/node_modules/clawdbot/dist/entry.js",
);
const realpathResolved = path.resolve(
"/Users/test/Library/pnpm/global/5/node_modules/.pnpm/clawdbot@2026.1.21-2/node_modules/clawdbot/dist/entry.js",
);
process.argv = ["node", symlinkPath];
fsMocks.realpath.mockResolvedValue(realpathResolved);
fsMocks.access.mockResolvedValue(undefined); // Both paths exist
const result = await resolveGatewayProgramArguments({ port: 18789 });
// Should use the symlinked path, not the realpath-resolved versioned path
expect(result.programArguments[1]).toBe(symlinkPath);
expect(result.programArguments[1]).not.toContain("@2026.1.21-2");
});
it("falls back to node_modules package dist when .bin path is not resolved", async () => {
const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot");
const indexPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/index.js");