Skip to content

Commit cc51f24

Browse files
vidhaniokhaneliman
authored andcommitted
claude-code: allow hooks to accept a path like skills/commands
programs.claude-code.hooks previously only accepted inline strings. It now shares the same either-lines-or-path content option used by agents, commands, and rules, so a hook script can be referenced from a file instead of being inlined. mkSourceEntry used lib.isPath, which does not match derivations such as pkgs.writeShellScript. Since lib.types.path accepts derivations, a derivation-backed hook (or agent, command, rule, or output style) sent the derivation to home.file.*.text and failed evaluation. Use lib.hm.strings.isPathLike instead, and add a derivation-backed hook test. No release-notes entry: other option additions/extensions to this module (e.g. path support for agents/commands, the rules option itself) haven't warranted one either.
1 parent 44a1a0a commit cc51f24

5 files changed

Lines changed: 65 additions & 20 deletions

File tree

modules/programs/claude-code/lib.nix

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ in
66
mkHelpers =
77
{ configDir }:
88
let
9-
mkSourceEntry = content: if lib.isPath content then { source = content; } else { text = content; };
9+
mkSourceEntry =
10+
content: if lib.hm.strings.isPathLike content then { source = content; } else { text = content; };
1011

1112
mkMarketplaceEntry = _name: content: {
1213
source = {
@@ -22,10 +23,7 @@ in
2223
attrs:
2324
lib.mapAttrs' (
2425
name: content:
25-
lib.nameValuePair "${configDir}/hooks/${name}" {
26-
text = content;
27-
executable = true;
28-
}
26+
lib.nameValuePair "${configDir}/hooks/${name}" ((mkSourceEntry content) // { executable = true; })
2927
) attrs;
3028

3129
mkInstalledMarketplaceEntry =

modules/programs/claude-code/options.nix

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -278,25 +278,24 @@ in
278278
'';
279279
};
280280

281-
hooks = mkOption {
282-
type = lib.types.attrsOf lib.types.lines;
283-
default = { };
281+
hooks = mkContentOption {
284282
description = ''
285283
Custom hooks for Claude Code.
286-
The attribute name becomes the hook filename, and the value is the hook script content.
284+
The attribute name becomes the hook filename, and the value is either:
285+
- Inline content as a string
286+
- A path to a file containing the hook script content
287287
Hooks are stored in the {file}`hooks/` subdirectory of
288-
{option}`programs.claude-code.configDir`.
288+
{option}`programs.claude-code.configDir` and made executable.
289+
'';
290+
example = literalExpression ''
291+
{
292+
pre-edit = '''
293+
#!/usr/bin/env bash
294+
echo "About to edit file: $1"
295+
''';
296+
post-commit = ./hooks/post-commit.sh;
297+
}
289298
'';
290-
example = {
291-
pre-edit = ''
292-
#!/usr/bin/env bash
293-
echo "About to edit file: $1"
294-
'';
295-
post-commit = ''
296-
#!/usr/bin/env bash
297-
echo "Committed with message: $1"
298-
'';
299-
};
300299
};
301300

302301
rules = mkContentOption {

tests/modules/programs/claude-code/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
claude-code-skills-subdir = ./skills-subdir.nix;
2222
claude-code-agents-path = ./agents-path.nix;
2323
claude-code-commands-path = ./commands-path.nix;
24+
claude-code-hooks-path = ./hooks-path.nix;
2425
claude-code-skills-path = ./skills-path.nix;
2526
claude-code-legacy-memory-text = ./legacy-memory-text.nix;
2627
claude-code-legacy-memory-source-and-skills-dir = ./legacy-memory-source-and-skills-dir.nix;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{ pkgs, ... }:
2+
let
3+
derivation-hook = pkgs.writeShellScript "derivation-hook" ''
4+
echo "About to edit file: $1"
5+
'';
6+
in
7+
{
8+
programs.claude-code = {
9+
enable = true;
10+
hooks = {
11+
inline-hook = ''
12+
#!/usr/bin/env bash
13+
echo "About to edit file: $1"
14+
'';
15+
test-hook = ./hooks/test-hook;
16+
inherit derivation-hook;
17+
};
18+
};
19+
20+
nmt.script = ''
21+
assertFileExists home-files/.claude/hooks/inline-hook
22+
assertFileIsExecutable home-files/.claude/hooks/inline-hook
23+
24+
assertFileExists home-files/.claude/hooks/test-hook
25+
assertFileIsExecutable home-files/.claude/hooks/test-hook
26+
assertFileContent home-files/.claude/hooks/test-hook \
27+
${./hooks/test-hook}
28+
29+
assertFileExists home-files/.claude/hooks/derivation-hook
30+
assertFileIsExecutable home-files/.claude/hooks/derivation-hook
31+
assertFileContent home-files/.claude/hooks/derivation-hook \
32+
${derivation-hook}
33+
'';
34+
}

tests/modules/programs/claude-code/mixed-content.nix

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
'';
3232
path-skill = ./test-skill.md;
3333
};
34+
hooks = {
35+
inline-hook = ''
36+
#!/usr/bin/env bash
37+
echo "This hook is defined inline."
38+
'';
39+
path-hook = ./hooks/test-hook;
40+
};
3441
};
3542

3643
nmt.script = ''
@@ -40,12 +47,18 @@
4047
assertFileExists home-files/.claude/agents/path-agent.md
4148
assertFileExists home-files/.claude/skills/inline-skill/SKILL.md
4249
assertFileExists home-files/.claude/skills/path-skill/SKILL.md
50+
assertFileExists home-files/.claude/hooks/inline-hook
51+
assertFileExists home-files/.claude/hooks/path-hook
4352
4453
assertFileContent home-files/.claude/commands/path-command.md \
4554
${./test-command.md}
4655
assertFileContent home-files/.claude/agents/path-agent.md \
4756
${./test-agent.md}
4857
assertFileContent home-files/.claude/skills/path-skill/SKILL.md \
4958
${./test-skill.md}
59+
assertFileContent home-files/.claude/hooks/path-hook \
60+
${./hooks/test-hook}
61+
assertFileIsExecutable home-files/.claude/hooks/inline-hook
62+
assertFileIsExecutable home-files/.claude/hooks/path-hook
5063
'';
5164
}

0 commit comments

Comments
 (0)