diff --git a/docs/init-hooks.md b/docs/init-hooks.md index eedfbd6eec..3207e6cfe9 100644 --- a/docs/init-hooks.md +++ b/docs/init-hooks.md @@ -27,6 +27,42 @@ chmod +x .cmux/init The init script runs in the workspace directory with the workspace's environment. +## Environment Variables + +Init hooks receive the following environment variables: + +- `MUX_PROJECT_PATH` - Absolute path to the project root on the **local machine** + - Always refers to your local project path, even on SSH workspaces + - Useful for logging, debugging, or runtime-specific logic +- `MUX_RUNTIME` - Runtime type: `"local"` or `"ssh"` + - Use this to detect whether the hook is running locally or remotely + +**Note for SSH workspaces:** Since the project is synced to the remote machine, files exist in both locations. The init hook runs in the workspace directory (`$PWD`), so use relative paths to reference project files: + +```bash +#!/bin/bash +set -e + +echo "Runtime: $MUX_RUNTIME" +echo "Local project path: $MUX_PROJECT_PATH" +echo "Workspace directory: $PWD" + +# Copy .env from project root (works for both local and SSH) +# The hook runs with cwd = workspace, and project root is the parent directory +if [ -f "../.env" ]; then + cp "../.env" "$PWD/.env" +fi + +# Runtime-specific behavior +if [ "$MUX_RUNTIME" = "local" ]; then + echo "Running on local machine" +else + echo "Running on SSH remote" +fi + +bun install +``` + ## Use Cases - Install dependencies (`npm install`, `bun install`, etc.) diff --git a/src/runtime/LocalRuntime.ts b/src/runtime/LocalRuntime.ts index e64c162ab9..f6750720e0 100644 --- a/src/runtime/LocalRuntime.ts +++ b/src/runtime/LocalRuntime.ts @@ -20,7 +20,12 @@ import { RuntimeError as RuntimeErrorClass } from "./Runtime"; import { NON_INTERACTIVE_ENV_VARS } from "../constants/env"; import { EXIT_CODE_ABORTED, EXIT_CODE_TIMEOUT } from "../constants/exitCodes"; import { listLocalBranches } from "../git"; -import { checkInitHookExists, getInitHookPath, createLineBufferedLoggers } from "./initHook"; +import { + checkInitHookExists, + getInitHookPath, + createLineBufferedLoggers, + getInitHookEnv, +} from "./initHook"; import { execAsync, DisposableProcess } from "../utils/disposableExec"; import { getProjectName } from "../utils/runtime/helpers"; import { getErrorMessage } from "../utils/errors"; @@ -415,6 +420,10 @@ export class LocalRuntime implements Runtime { const proc = spawn("bash", ["-c", `"${hookPath}"`], { cwd: workspacePath, stdio: ["ignore", "pipe", "pipe"], + env: { + ...process.env, + ...getInitHookEnv(projectPath, "local"), + }, }); proc.stdout.on("data", (data: Buffer) => { diff --git a/src/runtime/SSHRuntime.ts b/src/runtime/SSHRuntime.ts index c35eb2e357..902f1cf30b 100644 --- a/src/runtime/SSHRuntime.ts +++ b/src/runtime/SSHRuntime.ts @@ -18,7 +18,7 @@ import type { import { RuntimeError as RuntimeErrorClass } from "./Runtime"; import { EXIT_CODE_ABORTED, EXIT_CODE_TIMEOUT } from "../constants/exitCodes"; import { log } from "../services/log"; -import { checkInitHookExists, createLineBufferedLoggers } from "./initHook"; +import { checkInitHookExists, createLineBufferedLoggers, getInitHookEnv } from "./initHook"; import { streamProcessToLogger } from "./streamProcess"; import { expandTildeForSSH, cdCommandForSSH } from "./tildeExpansion"; import { getProjectName } from "../utils/runtime/helpers"; @@ -734,6 +734,7 @@ export class SSHRuntime implements Runtime { cwd: workspacePath, // Run in the workspace directory timeout: 3600, // 1 hour - generous timeout for init hooks abortSignal, + env: getInitHookEnv(projectPath, "ssh"), }); // Create line-buffered loggers diff --git a/src/runtime/initHook.ts b/src/runtime/initHook.ts index 401b71f009..70862f3cfb 100644 --- a/src/runtime/initHook.ts +++ b/src/runtime/initHook.ts @@ -26,6 +26,22 @@ export function getInitHookPath(projectPath: string): string { return path.join(projectPath, ".cmux", "init"); } +/** + * Get environment variables for init hook execution + * Centralizes env var injection to avoid duplication across runtimes + * @param projectPath - Path to project root (local path for LocalRuntime, remote path for SSHRuntime) + * @param runtime - Runtime type: "local" or "ssh" + */ +export function getInitHookEnv( + projectPath: string, + runtime: "local" | "ssh" +): Record { + return { + MUX_PROJECT_PATH: projectPath, + MUX_RUNTIME: runtime, + }; +} + /** * Line-buffered logger that splits stream output into lines and logs them * Handles incomplete lines by buffering until a newline is received