next-issue 0.12.0 → 0.12.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.
package/README.md CHANGED
@@ -85,9 +85,10 @@ only between two issues, finishes the issue that it holds, deletes the file and
85
85
  then writes the summary as usual. A file from an earlier run is dropped at the
86
86
  start, so it stops nothing.
87
87
 
88
- `Ctrl-C` is different: the terminal signals every child too, so the run dies in
89
- the middle of a step. The saved state lets the next run continue the issue, but
90
- the issue keeps `status:in-progress` and the run writes no summary.
88
+ `Ctrl-C` is different: the harness passes the signal on to the command that runs
89
+ and to every child of it, so the run dies in the middle of a step. The saved
90
+ state lets the next run continue the issue, but the issue keeps
91
+ `status:in-progress` and the run writes no summary.
91
92
 
92
93
  Every status label that the harness puts on the issue goes on the pull request
93
94
  too, from the first review round. Thus a draft pull request shows work that the
package/dist/exec.mjs CHANGED
@@ -2,6 +2,41 @@ import { spawn } from "node:child_process";
2
2
  let observer;
3
3
  let defaultTimeoutMs = 10 * 60_000;
4
4
  const STDERR_TAIL = 200_000;
5
+ const groups = new Set();
6
+ function forward(signal) {
7
+ for (const pid of groups) {
8
+ try {
9
+ process.kill(-pid, signal);
10
+ }
11
+ catch { }
12
+ }
13
+ groups.clear();
14
+ process.off("SIGINT", onInterrupt);
15
+ process.off("SIGTERM", onTerminate);
16
+ process.kill(process.pid, signal);
17
+ }
18
+ const onInterrupt = () => forward("SIGINT");
19
+ const onTerminate = () => forward("SIGTERM");
20
+ function watch(pid) {
21
+ if (pid === undefined) {
22
+ return;
23
+ }
24
+ if (groups.size === 0) {
25
+ process.on("SIGINT", onInterrupt);
26
+ process.on("SIGTERM", onTerminate);
27
+ }
28
+ groups.add(pid);
29
+ }
30
+ function unwatch(pid) {
31
+ if (pid === undefined) {
32
+ return;
33
+ }
34
+ groups.delete(pid);
35
+ if (groups.size === 0) {
36
+ process.off("SIGINT", onInterrupt);
37
+ process.off("SIGTERM", onTerminate);
38
+ }
39
+ }
5
40
  export function setCommandObserver(next) {
6
41
  observer = next;
7
42
  }
@@ -14,12 +49,15 @@ export function run(command, args, options = {}) {
14
49
  return new Promise((resolve, reject) => {
15
50
  const child = spawn(command, args, {
16
51
  cwd: options.cwd,
52
+ detached: true,
17
53
  stdio: [
18
54
  options.input === undefined ? "ignore" : "pipe",
19
55
  options.show === true ? 2 : "pipe",
20
56
  "pipe",
21
57
  ],
22
58
  });
59
+ const pid = child.pid;
60
+ watch(pid);
23
61
  let stdout = "";
24
62
  let stderr = "";
25
63
  let timedOut = false;
@@ -40,11 +78,21 @@ export function run(command, args, options = {}) {
40
78
  });
41
79
  const timer = setTimeout(() => {
42
80
  timedOut = true;
43
- child.kill("SIGKILL");
81
+ if (pid === undefined) {
82
+ child.kill("SIGKILL");
83
+ return;
84
+ }
85
+ try {
86
+ process.kill(-pid, "SIGKILL");
87
+ }
88
+ catch {
89
+ child.kill("SIGKILL");
90
+ }
44
91
  }, limit);
45
92
  let done = false;
46
93
  const settle = (code) => {
47
94
  clearTimeout(timer);
95
+ unwatch(pid);
48
96
  if (done) {
49
97
  return;
50
98
  }
@@ -63,6 +111,7 @@ export function run(command, args, options = {}) {
63
111
  };
64
112
  child.on("error", (error) => {
65
113
  clearTimeout(timer);
114
+ unwatch(pid);
66
115
  done = true;
67
116
  reject(error);
68
117
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-issue",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Autonomous issue harness driven by the Claude Agent SDK",
5
5
  "keywords": [
6
6
  "github",