mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
feat: auto-detect claude binary to prevent ENOENT in launchd (v1.8.0)
The proxy previously defaulted to bare "claude" command which fails in macOS LaunchAgent where PATH is minimal. Now resolves the binary at startup via: CLAUDE_BIN env → well-known paths → which fallback. Fails fast with clear error if binary cannot be found. Also enhances /health to report claudeBinary and claudeBinaryOk status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+45
-4
@@ -18,17 +18,54 @@
|
||||
* PROXY_API_KEY — Bearer token for API authentication (optional, if unset auth is disabled)
|
||||
*/
|
||||
import { createServer } from "node:http";
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, execFileSync } from "node:child_process";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { readFileSync, accessSync, constants } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const _pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8"));
|
||||
|
||||
// ── Resolve claude binary ───────────────────────────────────────────────
|
||||
// Priority: CLAUDE_BIN env > well-known paths > which lookup
|
||||
// Fail-fast if not found — never start with an unresolvable binary.
|
||||
function resolveClaude() {
|
||||
if (process.env.CLAUDE_BIN) {
|
||||
try {
|
||||
accessSync(process.env.CLAUDE_BIN, constants.X_OK);
|
||||
return process.env.CLAUDE_BIN;
|
||||
} catch {
|
||||
console.error(`FATAL: CLAUDE_BIN="${process.env.CLAUDE_BIN}" is set but not executable.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
"/opt/homebrew/bin/claude",
|
||||
"/usr/local/bin/claude",
|
||||
"/usr/bin/claude",
|
||||
join(process.env.HOME || "", ".local/bin/claude"),
|
||||
];
|
||||
for (const p of candidates) {
|
||||
try { accessSync(p, constants.X_OK); console.warn(`[init] CLAUDE_BIN not set, resolved to ${p}`); return p; } catch {}
|
||||
}
|
||||
|
||||
try {
|
||||
const resolved = execFileSync("which", ["claude"], { encoding: "utf8", timeout: 5000 }).trim();
|
||||
if (resolved) { console.warn(`[init] CLAUDE_BIN not set, resolved via which: ${resolved}`); return resolved; }
|
||||
} catch {}
|
||||
|
||||
console.error(
|
||||
"FATAL: claude binary not found.\n" +
|
||||
" Set CLAUDE_BIN=/path/to/claude or ensure claude is in PATH.\n" +
|
||||
" Checked: " + candidates.join(", ")
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const PORT = parseInt(process.env.CLAUDE_PROXY_PORT || "3456", 10);
|
||||
const CLAUDE = process.env.CLAUDE_BIN || "claude";
|
||||
const CLAUDE = resolveClaude();
|
||||
const TIMEOUT = parseInt(process.env.CLAUDE_TIMEOUT || "300000", 10);
|
||||
const POOL_SIZE = parseInt(process.env.CLAUDE_POOL_SIZE || "1", 10);
|
||||
const POOL_MAX_IDLE = parseInt(process.env.CLAUDE_POOL_MAX_IDLE || "60000", 10); // max idle time before recycle
|
||||
@@ -450,11 +487,15 @@ const server = createServer(async (req, res) => {
|
||||
};
|
||||
}
|
||||
const uptimeMs = Date.now() - START_TIME;
|
||||
let binaryOk = false;
|
||||
try { accessSync(CLAUDE, constants.X_OK); binaryOk = true; } catch {}
|
||||
return jsonResponse(res, 200, {
|
||||
status: "ok",
|
||||
status: binaryOk ? "ok" : "degraded",
|
||||
version: VERSION,
|
||||
uptime: uptimeMs,
|
||||
uptimeHuman: `${Math.floor(uptimeMs / 3600000)}h ${Math.floor((uptimeMs % 3600000) / 60000)}m ${Math.floor((uptimeMs % 60000) / 1000)}s`,
|
||||
claudeBinary: CLAUDE,
|
||||
claudeBinaryOk: binaryOk,
|
||||
pool: poolStatus,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user