@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.
- package/lib/daemon-health.js +34 -2
- package/lib/daemon.js +27 -2
- package/package.json +1 -1
package/lib/daemon-health.js
CHANGED
|
@@ -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:
|
|
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
|
|
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:
|
|
1828
|
+
env: selfUpdateEnv
|
|
1804
1829
|
});
|
|
1805
1830
|
writeFileSync(PID_FILE, String(child.pid));
|
|
1806
1831
|
child.unref();
|