Skip to content

Commit a810a93

Browse files
authored
🤖 feat: pass PROJECT_PATH env var to init hooks (#575)
Generated with `cmux` ## Changes Adds `PROJECT_PATH` environment variable to init hooks, providing access to the project root directory. ### Implementation - **LocalRuntime**: Pass `PROJECT_PATH` when spawning init hook process - **SSHRuntime**: Pass `PROJECT_PATH` in env when executing init hook remotely - **Centralized**: Extract `getInitHookEnv()` helper to avoid duplication ### Documentation Updated `docs/init-hooks.md` with: - New "Environment Variables" section documenting `PROJECT_PATH` - Example showing how to reference files in project root from workspace This allows init hooks to reliably access project-level files (like `.env` templates) without inferring paths from the workspace directory structure.
1 parent c91560e commit a810a93

File tree

4 files changed

+64
-2
lines changed

4 files changed

+64
-2
lines changed

docs/init-hooks.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,42 @@ chmod +x .cmux/init
2727

2828
The init script runs in the workspace directory with the workspace's environment.
2929

30+
## Environment Variables
31+
32+
Init hooks receive the following environment variables:
33+
34+
- `MUX_PROJECT_PATH` - Absolute path to the project root on the **local machine**
35+
- Always refers to your local project path, even on SSH workspaces
36+
- Useful for logging, debugging, or runtime-specific logic
37+
- `MUX_RUNTIME` - Runtime type: `"local"` or `"ssh"`
38+
- Use this to detect whether the hook is running locally or remotely
39+
40+
**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:
41+
42+
```bash
43+
#!/bin/bash
44+
set -e
45+
46+
echo "Runtime: $MUX_RUNTIME"
47+
echo "Local project path: $MUX_PROJECT_PATH"
48+
echo "Workspace directory: $PWD"
49+
50+
# Copy .env from project root (works for both local and SSH)
51+
# The hook runs with cwd = workspace, and project root is the parent directory
52+
if [ -f "../.env" ]; then
53+
cp "../.env" "$PWD/.env"
54+
fi
55+
56+
# Runtime-specific behavior
57+
if [ "$MUX_RUNTIME" = "local" ]; then
58+
echo "Running on local machine"
59+
else
60+
echo "Running on SSH remote"
61+
fi
62+
63+
bun install
64+
```
65+
3066
## Use Cases
3167

3268
- Install dependencies (`npm install`, `bun install`, etc.)

src/runtime/LocalRuntime.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import { RuntimeError as RuntimeErrorClass } from "./Runtime";
2020
import { NON_INTERACTIVE_ENV_VARS } from "../constants/env";
2121
import { EXIT_CODE_ABORTED, EXIT_CODE_TIMEOUT } from "../constants/exitCodes";
2222
import { listLocalBranches } from "../git";
23-
import { checkInitHookExists, getInitHookPath, createLineBufferedLoggers } from "./initHook";
23+
import {
24+
checkInitHookExists,
25+
getInitHookPath,
26+
createLineBufferedLoggers,
27+
getInitHookEnv,
28+
} from "./initHook";
2429
import { execAsync, DisposableProcess } from "../utils/disposableExec";
2530
import { getProjectName } from "../utils/runtime/helpers";
2631
import { getErrorMessage } from "../utils/errors";
@@ -415,6 +420,10 @@ export class LocalRuntime implements Runtime {
415420
const proc = spawn("bash", ["-c", `"${hookPath}"`], {
416421
cwd: workspacePath,
417422
stdio: ["ignore", "pipe", "pipe"],
423+
env: {
424+
...process.env,
425+
...getInitHookEnv(projectPath, "local"),
426+
},
418427
});
419428

420429
proc.stdout.on("data", (data: Buffer) => {

src/runtime/SSHRuntime.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
import { RuntimeError as RuntimeErrorClass } from "./Runtime";
1919
import { EXIT_CODE_ABORTED, EXIT_CODE_TIMEOUT } from "../constants/exitCodes";
2020
import { log } from "../services/log";
21-
import { checkInitHookExists, createLineBufferedLoggers } from "./initHook";
21+
import { checkInitHookExists, createLineBufferedLoggers, getInitHookEnv } from "./initHook";
2222
import { streamProcessToLogger } from "./streamProcess";
2323
import { expandTildeForSSH, cdCommandForSSH } from "./tildeExpansion";
2424
import { getProjectName } from "../utils/runtime/helpers";
@@ -734,6 +734,7 @@ export class SSHRuntime implements Runtime {
734734
cwd: workspacePath, // Run in the workspace directory
735735
timeout: 3600, // 1 hour - generous timeout for init hooks
736736
abortSignal,
737+
env: getInitHookEnv(projectPath, "ssh"),
737738
});
738739

739740
// Create line-buffered loggers

src/runtime/initHook.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ export function getInitHookPath(projectPath: string): string {
2626
return path.join(projectPath, ".cmux", "init");
2727
}
2828

29+
/**
30+
* Get environment variables for init hook execution
31+
* Centralizes env var injection to avoid duplication across runtimes
32+
* @param projectPath - Path to project root (local path for LocalRuntime, remote path for SSHRuntime)
33+
* @param runtime - Runtime type: "local" or "ssh"
34+
*/
35+
export function getInitHookEnv(
36+
projectPath: string,
37+
runtime: "local" | "ssh"
38+
): Record<string, string> {
39+
return {
40+
MUX_PROJECT_PATH: projectPath,
41+
MUX_RUNTIME: runtime,
42+
};
43+
}
44+
2945
/**
3046
* Line-buffered logger that splits stream output into lines and logs them
3147
* Handles incomplete lines by buffering until a newline is received

0 commit comments

Comments
 (0)