@masslessai/push-todo 3.10.7 → 3.10.9

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.
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
11
- import { spawn } from 'child_process';
11
+ import { spawn, execFileSync } from 'child_process';
12
12
  import { homedir } from 'os';
13
13
  import { join, dirname } from 'path';
14
14
  import { fileURLToPath } from 'url';
@@ -116,6 +116,30 @@ function formatUptime(startedAt) {
116
116
  }
117
117
  }
118
118
 
119
+ /**
120
+ * Kill any orphaned daemon processes not tracked by PID file.
121
+ * Prevents zombie daemons from racing the real daemon for tasks.
122
+ */
123
+ function killOrphanedDaemons() {
124
+ try {
125
+ const trackedPid = existsSync(PID_FILE)
126
+ ? parseInt(readFileSync(PID_FILE, 'utf8').trim(), 10)
127
+ : null;
128
+ const output = execFileSync('pgrep', ['-f', 'node.*daemon\\.js'], {
129
+ encoding: 'utf8',
130
+ timeout: 5000
131
+ }).trim();
132
+ for (const line of output.split('\n')) {
133
+ const pid = parseInt(line.trim(), 10);
134
+ if (!isNaN(pid) && pid !== trackedPid && pid !== process.pid) {
135
+ try { process.kill(pid, 'SIGTERM'); } catch {}
136
+ }
137
+ }
138
+ } catch {
139
+ // pgrep not found or no matches — both fine
140
+ }
141
+ }
142
+
119
143
  /**
120
144
  * Start the daemon process.
121
145
  * Same as Python's start_daemon().
@@ -126,6 +150,9 @@ export function startDaemon() {
126
150
  return true;
127
151
  }
128
152
 
153
+ // Kill any zombie daemons before starting fresh
154
+ killOrphanedDaemons();
155
+
129
156
  mkdirSync(PUSH_DIR, { recursive: true });
130
157
 
131
158
  const daemonScript = join(__dirname, 'daemon.js');
@@ -138,7 +165,12 @@ export function startDaemon() {
138
165
  const child = spawn(process.execPath, [daemonScript], {
139
166
  detached: true,
140
167
  stdio: ['ignore', 'ignore', 'ignore'],
141
- env: { ...process.env, PUSH_DAEMON: '1' }
168
+ env: (() => {
169
+ const env = { ...process.env, PUSH_DAEMON: '1' };
170
+ delete env.CLAUDECODE; // Strip to avoid leaking into Claude child processes
171
+ delete env.CLAUDE_CODE_ENTRYPOINT; // Strip to avoid any entrypoint-based guards
172
+ return env;
173
+ })()
142
174
  });
143
175
 
144
176
  writeFileSync(PID_FILE, String(child.pid));
package/lib/daemon.js CHANGED
@@ -92,6 +92,7 @@ const taskDetails = new Map(); // displayNumber -> details
92
92
  const completedToday = [];
93
93
  const taskLastOutput = new Map(); // displayNumber -> timestamp
94
94
  const taskStdoutBuffer = new Map(); // displayNumber -> lines[]
95
+ const taskStderrBuffer = new Map(); // displayNumber -> lines[]
95
96
  const taskProjectPaths = new Map(); // displayNumber -> projectPath
96
97
  let daemonStartTime = null;
97
98
 
@@ -1380,6 +1381,7 @@ IMPORTANT:
1380
1381
  env: (() => {
1381
1382
  const env = { ...process.env, PUSH_TASK_ID: task.id, PUSH_DISPLAY_NUMBER: String(displayNumber) };
1382
1383
  delete env.CLAUDECODE; // Strip to avoid "nested session" guard in Claude Code
1384
+ delete env.CLAUDE_CODE_ENTRYPOINT; // Strip to avoid any entrypoint-based guards
1383
1385
  return env;
1384
1386
  })()
1385
1387
  });
@@ -1395,6 +1397,20 @@ IMPORTANT:
1395
1397
  runningTasks.set(displayNumber, taskInfo);
1396
1398
  taskLastOutput.set(displayNumber, Date.now());
1397
1399
  taskStdoutBuffer.set(displayNumber, []);
1400
+ taskStderrBuffer.set(displayNumber, []);
1401
+
1402
+ // Monitor stderr (critical for diagnosing fast exits)
1403
+ child.stderr.on('data', (data) => {
1404
+ const lines = data.toString().split('\n');
1405
+ for (const line of lines) {
1406
+ if (line.trim()) {
1407
+ const buffer = taskStderrBuffer.get(displayNumber) || [];
1408
+ buffer.push(line);
1409
+ if (buffer.length > 20) buffer.shift();
1410
+ taskStderrBuffer.set(displayNumber, buffer);
1411
+ }
1412
+ }
1413
+ });
1398
1414
 
1399
1415
  // Monitor stdout
1400
1416
  child.stdout.on('data', (data) => {
@@ -1579,7 +1595,11 @@ async function handleTaskCompletion(displayNumber, exitCode) {
1579
1595
  sessionId
1580
1596
  });
1581
1597
  } else {
1582
- const stderr = taskInfo.process.stderr?.read()?.toString() || '';
1598
+ const stderrLines = taskStderrBuffer.get(displayNumber) || [];
1599
+ const stderr = stderrLines.join('\n');
1600
+ if (stderr) {
1601
+ log(`Task #${displayNumber} stderr: ${stderr.slice(0, 500)}`);
1602
+ }
1583
1603
 
1584
1604
  // Ask Claude to explain what went wrong (needs worktree path)
1585
1605
  const failureSummary = extractSemanticSummary(worktreePath, sessionId);
@@ -1614,6 +1634,7 @@ async function handleTaskCompletion(displayNumber, exitCode) {
1614
1634
  taskDetails.delete(displayNumber);
1615
1635
  taskLastOutput.delete(displayNumber);
1616
1636
  taskStdoutBuffer.delete(displayNumber);
1637
+ taskStderrBuffer.delete(displayNumber);
1617
1638
  taskProjectPaths.delete(displayNumber);
1618
1639
  updateStatusFile();
1619
1640
  }
@@ -1757,6 +1778,7 @@ async function checkTimeouts() {
1757
1778
  taskDetails.delete(displayNumber);
1758
1779
  taskLastOutput.delete(displayNumber);
1759
1780
  taskStdoutBuffer.delete(displayNumber);
1781
+ taskStderrBuffer.delete(displayNumber);
1760
1782
  taskProjectPaths.delete(displayNumber);
1761
1783
  cleanupWorktree(displayNumber, projectPath);
1762
1784
  }
@@ -1797,10 +1819,13 @@ function checkAndApplyUpdate() {
1797
1819
 
1798
1820
  // Spawn new daemon from updated code, then exit
1799
1821
  const daemonScript = join(__dirname, 'daemon.js');
1822
+ const selfUpdateEnv = { ...process.env, PUSH_DAEMON: '1' };
1823
+ delete selfUpdateEnv.CLAUDECODE; // Strip to avoid leaking into Claude child processes
1824
+ delete selfUpdateEnv.CLAUDE_CODE_ENTRYPOINT; // Strip to avoid any entrypoint-based guards
1800
1825
  const child = spawn(process.execPath, [daemonScript], {
1801
1826
  detached: true,
1802
1827
  stdio: ['ignore', 'ignore', 'ignore'],
1803
- env: { ...process.env, PUSH_DAEMON: '1' }
1828
+ env: selfUpdateEnv
1804
1829
  });
1805
1830
  writeFileSync(PID_FILE, String(child.pid));
1806
1831
  child.unref();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masslessai/push-todo",
3
- "version": "3.10.7",
3
+ "version": "3.10.9",
4
4
  "description": "Voice tasks from Push iOS app for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {