@hienlh/ppm 0.11.7 → 0.11.8

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.8] - 2026-04-19
4
+
5
+ ### Fixed
6
+ - **Supervisor shutdown timeout**: Use SIGKILL for child processes instead of SIGTERM — prevents 90s systemd timeout caused by orphaned grandchildren (Claude SDK subprocesses)
7
+ - **Supervisor signal handler**: Add 5s force-exit safety net if `process.exit(0)` doesn't terminate
8
+ - **Tunnel retry overflow**: URL extraction failure path now respects MAX_RESTARTS and resets counter after stable window (previously retried infinitely)
9
+
10
+ ### Improved
11
+ - **Supervisor logging**: All supervisor logs now write to stderr so `journalctl` captures full lifecycle events
12
+ - **Tunnel exit logging**: Log exit code and signal when tunnel process dies for easier debugging
13
+ - **systemd service hardening**: Add `TimeoutStopSec=10` and `KillMode=mixed` to generated service unit
14
+
3
15
  ## [0.11.7] - 2026-04-19
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.11.7",
3
+ "version": "0.11.8",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -134,6 +134,8 @@ Type=simple
134
134
  ExecStart=${execStart}
135
135
  Restart=on-failure
136
136
  RestartSec=5
137
+ TimeoutStopSec=10
138
+ KillMode=mixed
137
139
  ${envPath}
138
140
  WorkingDirectory=${homedir()}/.ppm
139
141
 
@@ -242,7 +242,18 @@ export async function spawnTunnel(port: number): Promise<void> {
242
242
  tunnelChild = null;
243
243
 
244
244
  if (shuttingDown) return;
245
+
246
+ const now = Date.now();
247
+ if (now - lastTunnelCrash > STABLE_WINDOW_MS) tunnelRestarts = 0;
248
+ lastTunnelCrash = now;
245
249
  tunnelRestarts++;
250
+
251
+ if (tunnelRestarts > MAX_RESTARTS) {
252
+ log("ERROR", `Tunnel exceeded ${MAX_RESTARTS} URL extraction failures, disabling tunnel`);
253
+ updateStatus({ shareUrl: null, tunnelPid: null });
254
+ return;
255
+ }
256
+
246
257
  const delay = backoffDelay(tunnelRestarts);
247
258
  log("WARN", `Tunnel failed, retry in ${delay}ms (#${tunnelRestarts})`);
248
259
  await Bun.sleep(delay);
@@ -275,7 +286,7 @@ export async function spawnTunnel(port: number): Promise<void> {
275
286
  }
276
287
 
277
288
  const delay = backoffDelay(tunnelRestarts);
278
- log("WARN", `Tunnel died (exit ${exitCode}, was ${deadUrl}), restart in ${delay}ms (#${tunnelRestarts})`);
289
+ log("WARN", `Tunnel process exited (code=${exitCode}, signal=${tunnelChild === null ? "killed" : "self"}, url=${deadUrl}), restart in ${delay}ms (#${tunnelRestarts})`);
279
290
  await Bun.sleep(delay);
280
291
 
281
292
  if (!shuttingDown) return spawnTunnel(port);