HookStack
Back to catalogue
WorkflowCwdChanged

Reload direnv on directory change

Bash always runs in the right env per folder

Automatically reloads environment variables via direnv when Claude changes its working directory, ensuring Bash commands use the right environment for the current directory.

What does the Reload direnv on directory change hook do?

Reload direnv on directory change is a Claude Code CwdChanged hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Bash always runs in the right env per folder.

As a CwdChanged hook it runs after the action, reacting to what just happened rather than blocking it. Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.

Use cases

  • Mono-repo projects with different environments per subfolder
  • Automatic .envrc management with direnv
  • Sync CLAUDE_ENV_FILE with the current directory's environment

Tags

#direnv#environment#cwd#monorepo

settings.json fragment

{
  "hooks": {
    "CwdChanged": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/reload-direnv.mjs",
            "type": "command"
          }
        ],
        "matcher": ""
      }
    ],
    "SessionStart": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/reload-direnv.mjs",
            "type": "command"
          }
        ],
        "matcher": ""
      }
    ]
  }
}

Script · .claude/hooks/reload-direnv.mjs

#!/usr/bin/env node
// @hookstack cwd-changed-reload-direnv
import { execSync } from "node:child_process";
// @hookstack cwd-changed-reload-direnv
// Recharge direnv quand le répertoire de travail change (CwdChanged)
import { existsSync, readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

function defaultExec(cmd, cwd) {
	execSync(cmd, { cwd, stdio: "ignore", timeout: 5_000 });
}

export function run(input, { exec = defaultExec, exists = existsSync } = {}) {
	const newCwd = input.cwd ?? input.new_cwd ?? process.cwd();
	const envrc = `${newCwd}/.envrc`;
	if (!exists(envrc)) return null;

	try {
		exec("direnv allow .", newCwd);
		return { message: `[reload-direnv] direnv rechargé dans ${newCwd}\n` };
	} catch {
		// direnv absent — non bloquant
		return null;
	}
}

/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
	const input = JSON.parse(readFileSync(0, "utf8"));
	const result = run(input);
	if (result?.message) process.stderr.write(result.message);
}

Learn more

Related hooks