fix: use npm install for Claude Code CLI on musl/Alpine systems
The native binary (install.sh) requires posix_getdents which is only available in Alpine edge (musl 1.2.5-r21+), not in any stable Alpine release. Detect musl upfront via ldd and skip straight to npm install, avoiding 15+ seconds of wasted retries on deterministic failures. On glibc systems the native install path is unchanged with npm as a fallback if it fails.
This commit is contained in:
1 parent
f9c9754a16
commit
b8cb56d204
1 file changed
+82
-10
+82
-10
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { dirname } from "path";
|
import { dirname } from "path";
|
||||||
import { spawn } from "child_process";
|
import { spawn, spawnSync } from "child_process";
|
||||||
import { appendFile } from "fs/promises";
|
import { appendFile } from "fs/promises";
|
||||||
import { existsSync, readFileSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
import { setupGitHubToken, WorkflowValidationSkipError } from "../github/token";
|
import { setupGitHubToken, WorkflowValidationSkipError } from "../github/token";
|
||||||
@@ -57,15 +57,53 @@ async function installClaudeCode(): Promise<void> {
|
|||||||
const claudeCodeVersion = "2.1.61";
|
const claudeCodeVersion = "2.1.61";
|
||||||
console.log(`Installing Claude Code v${claudeCodeVersion}...`);
|
console.log(`Installing Claude Code v${claudeCodeVersion}...`);
|
||||||
|
|
||||||
|
// On musl/Alpine systems, the native binary may require a newer musl than
|
||||||
|
// available (e.g. posix_getdents added in musl 1.2.5-r21, only in Alpine edge).
|
||||||
|
// Skip straight to npm install to avoid wasting time on deterministic failures.
|
||||||
|
if (isMusl()) {
|
||||||
|
console.log(
|
||||||
|
"Detected musl libc, using npm install (native binary may be incompatible)...",
|
||||||
|
);
|
||||||
|
await npmInstall(claudeCodeVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On glibc systems, try the native binary install with retry for transient failures.
|
||||||
|
const nativeInstallSuccess = await tryNativeInstall(claudeCodeVersion);
|
||||||
|
if (nativeInstallSuccess) return;
|
||||||
|
|
||||||
|
// Fall back to npm if native install fails on glibc too (shouldn't happen normally).
|
||||||
|
console.log("Native install failed, falling back to npm install...");
|
||||||
|
await npmInstall(claudeCodeVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if the system uses musl libc (Alpine, etc.).
|
||||||
|
*/
|
||||||
|
function isMusl(): boolean {
|
||||||
|
try {
|
||||||
|
const output = spawnSync("ldd", ["--version"], { encoding: "utf-8" });
|
||||||
|
// musl's ldd prints "musl libc" to stderr
|
||||||
|
return (output.stderr + output.stdout).toLowerCase().includes("musl");
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try installing Claude Code via the native install script.
|
||||||
|
* Returns true on success, false on failure.
|
||||||
|
*/
|
||||||
|
async function tryNativeInstall(version: string): Promise<boolean> {
|
||||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||||
console.log(`Installation attempt ${attempt}...`);
|
console.log(`Native installation attempt ${attempt}...`);
|
||||||
try {
|
try {
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const child = spawn(
|
const child = spawn(
|
||||||
"bash",
|
"bash",
|
||||||
[
|
[
|
||||||
"-c",
|
"-c",
|
||||||
`curl -fsSL https://claude.ai/install.sh | bash -s -- ${claudeCodeVersion}`,
|
`curl -fsSL https://claude.ai/install.sh | bash -s -- ${version}`,
|
||||||
],
|
],
|
||||||
{ stdio: "inherit" },
|
{ stdio: "inherit" },
|
||||||
);
|
);
|
||||||
@@ -75,25 +113,59 @@ async function installClaudeCode(): Promise<void> {
|
|||||||
});
|
});
|
||||||
child.on("error", reject);
|
child.on("error", reject);
|
||||||
});
|
});
|
||||||
console.log("Claude Code installed successfully");
|
console.log("Claude Code installed successfully (native)");
|
||||||
// Add to PATH
|
|
||||||
const homeBin = `${process.env.HOME}/.local/bin`;
|
const homeBin = `${process.env.HOME}/.local/bin`;
|
||||||
const githubPath = process.env.GITHUB_PATH;
|
const githubPath = process.env.GITHUB_PATH;
|
||||||
if (githubPath) {
|
if (githubPath) {
|
||||||
await appendFile(githubPath, `${homeBin}\n`);
|
await appendFile(githubPath, `${homeBin}\n`);
|
||||||
}
|
}
|
||||||
process.env.PATH = `${homeBin}:${process.env.PATH}`;
|
process.env.PATH = `${homeBin}:${process.env.PATH}`;
|
||||||
return;
|
return true;
|
||||||
} catch (error) {
|
} catch {
|
||||||
if (attempt === 3) {
|
if (attempt === 3) {
|
||||||
throw new Error(
|
return false;
|
||||||
`Failed to install Claude Code after 3 attempts: ${error}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
console.log("Installation failed, retrying...");
|
console.log("Installation failed, retrying...");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install Claude Code via npm as a fallback for musl/Alpine systems where
|
||||||
|
* the native binary may be incompatible (e.g. posix_getdents symbol missing).
|
||||||
|
*/
|
||||||
|
async function npmInstall(version: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const child = spawn(
|
||||||
|
"npm",
|
||||||
|
["install", "-g", `@anthropic-ai/claude-code@${version}`],
|
||||||
|
{ stdio: "inherit" },
|
||||||
|
);
|
||||||
|
child.on("close", (code) => {
|
||||||
|
if (code === 0) resolve();
|
||||||
|
else reject(new Error(`npm install failed with exit code ${code}`));
|
||||||
|
});
|
||||||
|
child.on("error", reject);
|
||||||
|
});
|
||||||
|
console.log("Claude Code installed successfully (npm)");
|
||||||
|
// Resolve the npm global bin directory and ensure it's in PATH.
|
||||||
|
// Use `npm prefix -g` + /bin (npm bin -g is deprecated since npm v9).
|
||||||
|
const npmPrefix = spawnSync("npm", ["prefix", "-g"], { encoding: "utf-8" });
|
||||||
|
const npmBin =
|
||||||
|
npmPrefix.status === 0 ? `${npmPrefix.stdout.trim()}/bin` : "";
|
||||||
|
if (npmBin) {
|
||||||
|
const githubPath = process.env.GITHUB_PATH;
|
||||||
|
if (githubPath) {
|
||||||
|
await appendFile(githubPath, `${npmBin}\n`);
|
||||||
|
}
|
||||||
|
process.env.PATH = `${npmBin}:${process.env.PATH}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to install Claude Code via npm: ${error}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user