Skip to content
Open
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
18 changes: 11 additions & 7 deletions packages/plugins/agent-claude-code/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,14 +902,18 @@ describe("hook setup — relative path (symlink-safe)", () => {
return parsed.hooks.PostToolUse[0].hooks[0].command;
}

it("setupWorkspaceHooks writes a relative hook command (not absolute)", async () => {
it("setupWorkspaceHooks writes a $CLAUDE_PROJECT_DIR-based hook command (not a hardcoded absolute path)", async () => {
await agent.setupWorkspaceHooks!(
"/Users/equinox/.worktrees/integrator/integrator-5",
{} as WorkspaceHooksConfig,
);

const hookCommand = getWrittenHookCommand();
expect(hookCommand).toBe(".claude/metadata-updater.sh");
// $CLAUDE_PROJECT_DIR resolves to the worktree root at hook-execution time,
// so the command works even when a hook fires from a sub-agent (Agent/Task)
// whose cwd is not the worktree root — while keeping settings.json content
// identical across worktrees (no hardcoded path).
expect(hookCommand).toBe("$CLAUDE_PROJECT_DIR/.claude/metadata-updater.sh");
expect(hookCommand).not.toMatch(/^\//);
});

Expand Down Expand Up @@ -950,7 +954,7 @@ describe("hook setup — relative path (symlink-safe)", () => {
expect(content1).toBe(content2);
});

it("updates an existing absolute hook path to relative", async () => {
it("updates an existing absolute hook path to the $CLAUDE_PROJECT_DIR form", async () => {
mockExistsSync.mockReturnValue(true);
mockReadFile.mockResolvedValue(
JSON.stringify({
Expand Down Expand Up @@ -978,7 +982,7 @@ describe("hook setup — relative path (symlink-safe)", () => {
);

const hookCommand = getWrittenHookCommand();
expect(hookCommand).toBe(".claude/metadata-updater.sh");
expect(hookCommand).toBe("$CLAUDE_PROJECT_DIR/.claude/metadata-updater.sh");
});

it("still writes the script file to the correct absolute filesystem path", async () => {
Expand Down Expand Up @@ -1021,8 +1025,8 @@ describe("setupWorkspaceHooks — activity-updater (#1941)", () => {
}

/** Activity-updater command paths (unix vs win32) */
const ACTIVITY_CMD_UNIX = ".claude/activity-updater.sh";
const ACTIVITY_CMD_WIN = "node .claude/activity-updater.cjs";
const ACTIVITY_CMD_UNIX = "$CLAUDE_PROJECT_DIR/.claude/activity-updater.sh";
const ACTIVITY_CMD_WIN = "node $CLAUDE_PROJECT_DIR/.claude/activity-updater.cjs";

/**
* Every Claude Code hook event the script knows how to translate into an
Expand Down Expand Up @@ -1326,7 +1330,7 @@ describe("setupWorkspaceHooks on win32", () => {
await agent.setupWorkspaceHooks!("C:\\\\Users\\\\dev\\\\workspace", {} as WorkspaceHooksConfig);

const hookCommand = getWrittenHookCommand();
expect(hookCommand).toBe("node .claude/metadata-updater.cjs");
expect(hookCommand).toBe("node $CLAUDE_PROJECT_DIR/.claude/metadata-updater.cjs");
expect(hookCommand).not.toContain(".sh");
});

Expand Down
8 changes: 4 additions & 4 deletions packages/plugins/agent-claude-code/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,17 +1009,17 @@ async function setupHookInWorkspace(workspacePath: string): Promise<void> {
await writeFile(activityPath, ACTIVITY_UPDATER_SCRIPT_NODE, "utf-8");
// .cjs forces CJS regardless of workspace package.json "type"; node
// invocation is required on Windows because shebangs aren't honoured.
metadataCommand = "node .claude/metadata-updater.cjs";
activityCommand = "node .claude/activity-updater.cjs";
metadataCommand = "node $CLAUDE_PROJECT_DIR/.claude/metadata-updater.cjs";
activityCommand = "node $CLAUDE_PROJECT_DIR/.claude/activity-updater.cjs";
} else {
const metadataPath = join(claudeDir, "metadata-updater.sh");
const activityPath = join(claudeDir, "activity-updater.sh");
await writeFile(metadataPath, METADATA_UPDATER_SCRIPT, "utf-8");
await writeFile(activityPath, ACTIVITY_UPDATER_SCRIPT, "utf-8");
await chmod(metadataPath, 0o755);
await chmod(activityPath, 0o755);
metadataCommand = ".claude/metadata-updater.sh";
activityCommand = ".claude/activity-updater.sh";
metadataCommand = "$CLAUDE_PROJECT_DIR/.claude/metadata-updater.sh";
activityCommand = "$CLAUDE_PROJECT_DIR/.claude/activity-updater.sh";
}

let existingSettings: Record<string, unknown> = {};
Expand Down