fix: resolve symlinks in auto-update path detection (#348)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2025-12-22 15:26:50 -08:00
committed by GitHub
parent 192a056b05
commit 73a964f111

View File

@@ -1,9 +1,18 @@
import { exec } from "node:child_process";
import { realpathSync } from "node:fs";
import { promisify } from "node:util";
import { getVersion } from "../version";
const execAsync = promisify(exec);
// Debug logging - set LETTA_DEBUG_AUTOUPDATE=1 to enable
const DEBUG = process.env.LETTA_DEBUG_AUTOUPDATE === "1";
function debugLog(...args: unknown[]) {
if (DEBUG) {
console.error("[auto-update]", ...args);
}
}
interface UpdateCheckResult {
updateAvailable: boolean;
latestVersion?: string;
@@ -17,30 +26,48 @@ function isAutoUpdateEnabled(): boolean {
function isRunningLocally(): boolean {
const argv = process.argv[1] || "";
// Resolve symlinks to get the real path
// npm creates symlinks in /bin/ that point to /lib/node_modules/
// Without resolving, argv would be like ~/.nvm/.../bin/letta (no node_modules)
let resolvedPath = argv;
try {
resolvedPath = realpathSync(argv);
} catch {
// If realpath fails (file doesn't exist), use original path
}
debugLog("argv[1]:", argv);
debugLog("resolved path:", resolvedPath);
// If running from node_modules, it's npm installed (should auto-update)
// Otherwise it's local dev (source or built locally)
return !argv.includes("node_modules");
return !resolvedPath.includes("node_modules");
}
async function checkForUpdate(): Promise<UpdateCheckResult> {
const currentVersion = getVersion();
debugLog("Current version:", currentVersion);
try {
debugLog("Checking npm for latest version...");
const { stdout } = await execAsync(
"npm view @letta-ai/letta-code version",
{ timeout: 5000 },
);
const latestVersion = stdout.trim();
debugLog("Latest version from npm:", latestVersion);
if (latestVersion !== currentVersion) {
debugLog("Update available!");
return {
updateAvailable: true,
latestVersion,
currentVersion,
};
}
} catch (_error) {
// Silently fail
debugLog("Already on latest version");
} catch (error) {
debugLog("Failed to check for updates:", error);
}
return {
@@ -51,11 +78,14 @@ async function checkForUpdate(): Promise<UpdateCheckResult> {
async function performUpdate(): Promise<{ success: boolean; error?: string }> {
try {
debugLog("Running npm install -g @letta-ai/letta-code@latest...");
await execAsync("npm install -g @letta-ai/letta-code@latest", {
timeout: 60000,
});
debugLog("Update completed successfully");
return { success: true };
} catch (error) {
debugLog("Update failed:", error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
@@ -64,7 +94,18 @@ async function performUpdate(): Promise<{ success: boolean; error?: string }> {
}
export async function checkAndAutoUpdate() {
if (!isAutoUpdateEnabled() || isRunningLocally()) {
debugLog("Auto-update check starting...");
debugLog("isAutoUpdateEnabled:", isAutoUpdateEnabled());
const runningLocally = isRunningLocally();
debugLog("isRunningLocally:", runningLocally);
if (!isAutoUpdateEnabled()) {
debugLog("Auto-update disabled via DISABLE_AUTOUPDATER=1");
return;
}
if (runningLocally) {
debugLog("Running locally, skipping auto-update");
return;
}