MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / runHook

Function runHook

src/hooks/executor.ts:20–145  ·  view source on GitHub ↗
(hook: HookConfig, ctx: HookContext)

Source from the content-addressed store, hash-verified

18 * a config file. We do redact sensitive arg values from the JSON we expose via env.
19 */
20export async function runHook(hook: HookConfig, ctx: HookContext): Promise<HookRunResult> {
21 const start = Date.now();
22 const timeoutMs = Math.max(1, hook.timeout ?? 30) * 1000;
23 const hookName = hook.name ?? hook.command.slice(0, 60);
24
25 // Build env. Redact sensitive arg values so a logger hook can't accidentally
26 // exfiltrate API keys etc. that the model passed to a tool.
27 const env: Record<string, string> = {
28 ...(process.env as Record<string, string>),
29 QODEX_HOOK_EVENT: ctx.event,
30 QODEX_SESSION_ID: ctx.sessionId,
31 QODEX_CWD: ctx.cwd,
32 };
33 if (ctx.toolName) env.QODEX_TOOL_NAME = ctx.toolName;
34 if (ctx.toolArgsJson) {
35 // Re-parse, redact, re-serialise so secret values don't leak via env
36 try {
37 const parsed = JSON.parse(ctx.toolArgsJson);
38 env.QODEX_TOOL_ARGS_JSON = JSON.stringify(
39 parsed && typeof parsed === 'object' && !Array.isArray(parsed)
40 ? redactObject(parsed)
41 : parsed,
42 );
43 } catch (e: any) {
44 // Redaction failed — NEVER fall back to the raw args (would leak secrets
45 // into the hook subprocess env). Use a safe placeholder instead.
46 logger.warn('Tool arg redaction failed; passing placeholder to hook', { hookName, err: e?.message });
47 env.QODEX_TOOL_ARGS_JSON = '[redaction failed]';
48 }
49 }
50 if (ctx.toolResult !== undefined) {
51 // Truncate to avoid huge env entries (some systems have ARG_MAX limits)
52 env.QODEX_TOOL_RESULT = ctx.toolResult.length > 64 * 1024
53 ? ctx.toolResult.slice(0, 64 * 1024) + '\n...[truncated]'
54 : ctx.toolResult;
55 }
56 if (ctx.filePaths && ctx.filePaths.length > 0) {
57 env.QODEX_FILE_PATHS = ctx.filePaths.join(' ');
58 }
59
60 return new Promise<HookRunResult>((resolve) => {
61 let proc: ReturnType<typeof spawn>;
62 try {
63 proc = spawn(hook.command, [], {
64 shell: true,
65 cwd: hook.cwd ?? ctx.cwd,
66 env,
67 stdio: ['ignore', 'pipe', 'pipe'],
68 // Run in its OWN process group so a timeout can kill the whole tree, not
69 // just the shell. With `shell: true` the command runs as `sh -c "<cmd>"`;
70 // SIGTERM to the shell alone can orphan its children (e.g. `sleep`), which
71 // keep the stdout pipe open so `close` never fires until they exit on their
72 // own — the hook timeout then never takes effect. Killing the group fixes it.
73 // (Windows has no POSIX process groups; cross-spawn handles kill there.)
74 detached: process.platform !== 'win32',
75 });
76 } catch (e: any) {
77 logger.warn(`Hook spawn failed`, { hookName, err: e.message });

Callers 2

hooks.test.tsFile · 0.85
dispatchMethod · 0.85

Calls 4

redactObjectFunction · 0.85
killTreeFunction · 0.85
warnMethod · 0.80
settleFunction · 0.70

Tested by

no test coverage detected