Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/init-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down
11 changes: 10 additions & 1 deletion src/runtime/LocalRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) => {
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/SSHRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions src/runtime/initHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> {
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
Expand Down