@fyeeme/pi-hooks 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/index.ts +23 -6
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.1] - 2025-07-15
11
+
12
+ ### Fixed
13
+
14
+ - Process hang when hook command ignores SIGTERM: added SIGKILL escalation after 5s grace window
15
+ - Race condition between `close` and `error` events: added `finish()` guard to prevent double-resolve
16
+
10
17
  ## [1.0.0] - 2025-05-31
11
18
 
12
19
  ### Added
package/index.ts CHANGED
@@ -110,27 +110,44 @@ async function runCommand(command: string, cwd: string, stdinJson: unknown): Pro
110
110
  });
111
111
 
112
112
  let stdout = "";
113
+ let settled = false;
114
+ const finish = (result: HookOutput | null) => {
115
+ if (settled) return;
116
+ settled = true;
117
+ clearTimeout(sigtermTimer);
118
+ clearTimeout(sigkillTimer);
119
+ resolve(result);
120
+ };
121
+
113
122
  proc.stdout?.on("data", (chunk: Buffer) => {
114
123
  stdout += chunk.toString();
115
124
  });
116
125
 
117
- const timer = setTimeout(() => proc.kill("SIGTERM"), 10_000);
126
+ let sigkillTimer: NodeJS.Timeout | undefined;
127
+
128
+ const sigtermTimer = setTimeout(() => {
129
+ try { proc.kill("SIGTERM"); } catch { /* already dead */ }
130
+ // escalate to SIGKILL after a 5s grace window
131
+ sigkillTimer = setTimeout(() => {
132
+ try { proc.kill("SIGKILL"); } catch { /* already dead */ }
133
+ // force-resolve: a process that survives SIGKILL is unrecoverable
134
+ finish(null);
135
+ }, 5_000);
136
+ }, 10_000);
118
137
 
119
138
  proc.on("close", (code) => {
120
- clearTimeout(timer);
121
139
  if (code !== 0) console.error(`[hooks] exited ${code}: ${command}`);
122
140
  try {
123
141
  const trimmed = stdout.trim();
124
- resolve(trimmed ? (JSON.parse(trimmed) as HookOutput) : null);
142
+ finish(trimmed ? (JSON.parse(trimmed) as HookOutput) : null);
125
143
  } catch {
126
- resolve(null);
144
+ finish(null);
127
145
  }
128
146
  });
129
147
 
130
148
  proc.on("error", (err) => {
131
- clearTimeout(timer);
132
149
  console.error(`[hooks] spawn error: ${command}: ${err}`);
133
- resolve(null);
150
+ finish(null);
134
151
  });
135
152
 
136
153
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fyeeme/pi-hooks",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Claude Code-compatible hooks runner for pi. Reads .pi/hooks.json and maps SessionStart, PreToolUse, and Stop events to pi lifecycle events.",
5
5
  "type": "module",
6
6
  "license": "MIT",