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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1535,10 +1535,10 @@ entries into the file's `hooks`:
```json
{
"hooks": {
"WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }],
"WorktreeRemove": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }],
"SubagentStart": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }],
"SubagentStop": [{ "hooks": [{ "type": "command", "command": "quest worktree hook" }] }]
"WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "command -v quest >/dev/null || { echo 'quest is not on the PATH Claude Code runs hooks with: run nix develop --command claude in this repository, or install quest on that PATH' >&2; exit 1; }; quest worktree hook" }] }],
"WorktreeRemove": [{ "hooks": [{ "type": "command", "command": "command -v quest >/dev/null || { echo 'quest is not on the PATH Claude Code runs hooks with: run nix develop --command claude in this repository, or install quest on that PATH' >&2; exit 1; }; quest worktree hook" }] }],
"SubagentStart": [{ "hooks": [{ "type": "command", "command": "command -v quest >/dev/null || { echo 'quest is not on the PATH Claude Code runs hooks with: run nix develop --command claude in this repository, or install quest on that PATH' >&2; exit 1; }; quest worktree hook" }] }],
"SubagentStop": [{ "hooks": [{ "type": "command", "command": "command -v quest >/dev/null || { echo 'quest is not on the PATH Claude Code runs hooks with: run nix develop --command claude in this repository, or install quest on that PATH' >&2; exit 1; }; quest worktree hook" }] }]
}
}
```
Expand Down
1 change: 0 additions & 1 deletion quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@ additional login providers. This milestone does not gate the public release.
- [Donor isolation](/quest/m2/donor-isolation.md) - stronger boundaries for untrusted shared jobs
- [ChatGPT hosted runs](/quest/m2/chatgpt-hosted.md) - conditional on provider approval and current terms
- [ChatGPT approval](/quest/m2/chatgpt-waitlist.md) - resolve hosted-use approval before implementation
- [Worktree hook PATH error](/quest/m2/hook-path-error.md) - a worktree hook without `quest` on `PATH` says so and names the dev-shell launch route
25 changes: 0 additions & 25 deletions quest/m2/hook-path-error.md

This file was deleted.

32 changes: 31 additions & 1 deletion test/worktree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { type Owner, Retained, type Scopes, currentOwner, kill, killable } from
import { GitHub, mergedHead } from "../src/cli/github";
import { main } from "../src/cli/main";
import { Worktrees, canonical, derivedDefaults, diskUsage } from "../src/cli/worktree";
import { claudeHook } from "../src/cli/worktree-hook";
import { HOOK_EVENTS, claudeHook } from "../src/cli/worktree-hook";
import { initRepository, run, template } from "./fixture";

const source = dirname(dirname(fileURLToPath(import.meta.url)));
Expand Down Expand Up @@ -1090,4 +1090,34 @@ describe("Claude Code hooks", { timeout: 20_000 }, () => {
1,
);
});
it.skipIf(process.platform === "win32")("the documented entries say so when quest is not on PATH", () => {
const readme = readFileSync(join(source, "README.md"), "utf8");
const json = [...readme.matchAll(/```json\n(.*?)```/gs)]
.map((match) => match[1])
.find((text) => text.includes('"WorktreeCreate"'));
if (json === undefined) throw new Error("README has no hook settings block");
const { hooks } = JSON.parse(json) as { hooks: Record<string, { hooks: { command: string }[] }[]> };
const commands = HOOK_EVENTS.map((event) => hooks[event][0].hooks[0].command);
expect(new Set(commands).size).toBe(1);
const [command] = commands;
const run = (path: string, input = "") =>
spawnSync("/bin/sh", ["-c", command], { input, encoding: "utf8", env: { PATH: path } });
const dir = (): string => {
const root = mkdtempSync(join(tmpdir(), "quest-hook-"));
roots.push(root);
return root;
};
const missing = run(dir());
expect(missing.status).toBe(1);
expect(missing.stdout).toBe("");
expect(missing.stderr).toBe(
"quest is not on the PATH Claude Code runs hooks with: run nix develop --command claude in this repository, or install quest on that PATH\n",
);
const bin = dir();
writeFileSync(join(bin, "quest"), '#!/bin/sh\necho "quest $*"\ncat\n', { mode: 0o755 });
const input = JSON.stringify({ hook_event_name: "WorktreeCreate", cwd: "/repo", name: "agent-1" });
const present = run(`${bin}:${process.env.PATH}`, input);
expect(present.status).toBe(0);
expect(present.stdout).toBe(`quest worktree hook\n${input}`);
});
});