@hienlh/ppm 0.9.61 → 0.9.62

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.62] - 2026-04-08
4
+
5
+ ### Fixed
6
+ - **SDK subprocess crash auto-retry**: When the Claude Code subprocess crashes (exit code 1), PPM now automatically retries once with a fresh subprocess after a 1s delay, instead of immediately showing the error. Only surfaces the crash message if the retry also fails.
7
+
3
8
  ## [0.9.61] - 2026-04-08
4
9
 
5
10
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.9.61",
3
+ "version": "0.9.62",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -702,6 +702,13 @@ export class ClaudeAgentSdkProvider implements AIProvider {
702
702
  includePartialMessages: true,
703
703
  };
704
704
 
705
+ // Crash retry: if subprocess exits with non-zero code before producing events,
706
+ // clean up and retry once with a fresh query before surfacing the error.
707
+ const MAX_CRASH_RETRIES = 1;
708
+ let crashRetryCount = 0;
709
+
710
+ crashRetryLoop: for (;;) {
711
+ try {
705
712
  // Streaming input: create message channel and persistent query
706
713
  const { generator: streamGen, controller: streamCtrl } = createMessageChannel();
707
714
  const firstMsg = {
@@ -1373,28 +1380,45 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1373
1380
  }
1374
1381
  break; // Exit retryLoop — normal completion
1375
1382
  } // end retryLoop
1376
- } catch (e) {
1377
- const msg = (e as Error).message ?? String(e);
1378
- console.error(`[sdk] session=${sessionId} cwd=${meta.projectPath} error: ${msg}`);
1379
- if (msg.includes("abort") || msg.includes("closed")) {
1383
+ break crashRetryLoop; // Normal completion — exit crash retry loop
1384
+ } catch (crashErr) {
1385
+ const crashMsg = (crashErr as Error).message ?? String(crashErr);
1386
+ console.error(`[sdk] session=${sessionId} cwd=${meta.projectPath} error: ${crashMsg}`);
1387
+
1388
+ // Clean up crashed subprocess before retry or error
1389
+ this.activeQueries.delete(sessionId);
1390
+ const ss = this.streamingSessions.get(sessionId);
1391
+ if (ss) { ss.controller.done(); ss.query.close(); this.streamingSessions.delete(sessionId); }
1392
+ console.log(`[sdk] session=${sessionId} streaming session ended`);
1393
+
1394
+ if (crashMsg.includes("abort") || crashMsg.includes("closed")) {
1380
1395
  // User-initiated abort or WS closed — nothing to report
1381
- } else if (msg.includes("exited with code")) {
1382
- // Subprocess crashed — session will auto-recover on next message
1383
- console.warn(`[sdk] session=${sessionId} subprocess crashed: ${msg}`);
1396
+ } else if (crashMsg.includes("exited with code") && crashRetryCount < MAX_CRASH_RETRIES) {
1397
+ // Subprocess crashed — auto-retry once before surfacing the error
1398
+ crashRetryCount++;
1399
+ console.warn(`[sdk] session=${sessionId} subprocess crashed: ${crashMsg} — auto-retrying (attempt ${crashRetryCount}/${MAX_CRASH_RETRIES})`);
1400
+ await new Promise((r) => setTimeout(r, 1000));
1401
+ continue crashRetryLoop;
1402
+ } else if (crashMsg.includes("exited with code")) {
1403
+ console.warn(`[sdk] session=${sessionId} subprocess crashed after retry: ${crashMsg}`);
1384
1404
  yield { type: "error", message: `SDK subprocess crashed. Send another message to auto-recover.` };
1385
1405
  } else {
1386
- yield { type: "error", message: `SDK error: ${msg}` };
1406
+ yield { type: "error", message: `SDK error: ${crashMsg}` };
1387
1407
  }
1408
+ break crashRetryLoop; // Exit after error handling (non-retryable)
1409
+ }
1410
+ } // end crashRetryLoop
1411
+
1412
+ } catch (outerErr) {
1413
+ // Setup errors (account auth, env) — not retryable
1414
+ const msg = (outerErr as Error).message ?? String(outerErr);
1415
+ console.error(`[sdk] session=${sessionId} setup error: ${msg}`);
1416
+ yield { type: "error", message: `SDK error: ${msg}` };
1388
1417
  } finally {
1418
+ // Final cleanup — ensure no leaked streaming session
1389
1419
  this.activeQueries.delete(sessionId);
1390
- // Properly close streaming session: terminate subprocess + generator
1391
1420
  const ss = this.streamingSessions.get(sessionId);
1392
- if (ss) {
1393
- ss.controller.done();
1394
- ss.query.close();
1395
- this.streamingSessions.delete(sessionId);
1396
- }
1397
- console.log(`[sdk] session=${sessionId} streaming session ended`);
1421
+ if (ss) { ss.controller.done(); ss.query.close(); this.streamingSessions.delete(sessionId); }
1398
1422
  }
1399
1423
 
1400
1424
  // Final done event when query ends (crash, close, generator done)