patchrelay 0.12.7 → 0.12.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.12.7",
4
- "commit": "b46978c9149d",
5
- "builtAt": "2026-03-24T22:34:10.024Z"
3
+ "version": "0.12.8",
4
+ "commit": "711863ef2d94",
5
+ "builtAt": "2026-03-24T22:49:07.152Z"
6
6
  }
@@ -48,6 +48,15 @@ export class CodexAppServerClient extends EventEmitter {
48
48
  this.child = this.spawnProcess(launch.command, launch.args, {
49
49
  stdio: ["pipe", "pipe", "pipe"],
50
50
  });
51
+ this.child.stdin.on("error", (error) => {
52
+ this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stdin error");
53
+ });
54
+ this.child.stdout.on("error", (error) => {
55
+ this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stdout error");
56
+ });
57
+ this.child.stderr.on("error", (error) => {
58
+ this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stderr error");
59
+ });
51
60
  this.child.stderr.on("data", (chunk) => {
52
61
  const line = chunk.toString().trim();
53
62
  if (line) {
@@ -75,6 +84,13 @@ export class CodexAppServerClient extends EventEmitter {
75
84
  });
76
85
  this.child.stdout.on("data", (chunk) => {
77
86
  this.stdoutBuffer += chunk.toString("utf8");
87
+ if (this.stdoutBuffer.length > 50 * 1024 * 1024) {
88
+ this.logger.error({ bufferSize: this.stdoutBuffer.length }, "Codex app-server stdout buffer exceeded 50 MB — killing process");
89
+ this.stdoutBuffer = "";
90
+ this.rejectAllPending(new Error("Codex app-server stdout buffer overflow"));
91
+ this.child?.kill("SIGTERM");
92
+ return;
93
+ }
78
94
  this.drainMessages();
79
95
  });
80
96
  const initializeResponse = await this.sendRequest("initialize", {
@@ -96,14 +112,24 @@ export class CodexAppServerClient extends EventEmitter {
96
112
  this.started = true;
97
113
  }
98
114
  async stop() {
99
- if (!this.child) {
115
+ const child = this.child;
116
+ if (!child) {
100
117
  return;
101
118
  }
102
119
  this.logger.info("Stopping Codex app-server");
103
120
  this.stopping = true;
104
- this.child.kill("SIGTERM");
105
- this.child = undefined;
106
121
  this.started = false;
122
+ const exited = new Promise((resolve) => {
123
+ child.on("close", () => resolve());
124
+ });
125
+ child.kill("SIGTERM");
126
+ this.child = undefined;
127
+ // Wait for the child to exit, but don't block shutdown forever.
128
+ const timeout = new Promise((resolve) => {
129
+ const timer = setTimeout(resolve, 10_000);
130
+ timer.unref?.();
131
+ });
132
+ await Promise.race([exited, timeout]);
107
133
  }
108
134
  async startThread(options) {
109
135
  const params = {
package/dist/http.js CHANGED
@@ -312,6 +312,12 @@ export async function buildHttpServer(config, service, logger) {
312
312
  for (const event of service.listOperatorFeed(feedQuery)) {
313
313
  writeEvent(event);
314
314
  }
315
+ const cleanup = () => {
316
+ clearInterval(keepAlive);
317
+ unsubscribe();
318
+ if (!reply.raw.destroyed)
319
+ reply.raw.end();
320
+ };
315
321
  const unsubscribe = service.subscribeOperatorFeed((event) => {
316
322
  if (!matchesOperatorFeedEvent(event, feedQuery)) {
317
323
  return;
@@ -321,11 +327,8 @@ export async function buildHttpServer(config, service, logger) {
321
327
  const keepAlive = setInterval(() => {
322
328
  reply.raw.write(": keepalive\n\n");
323
329
  }, 15000);
324
- request.raw.on("close", () => {
325
- clearInterval(keepAlive);
326
- unsubscribe();
327
- reply.raw.end();
328
- });
330
+ reply.raw.on("error", cleanup);
331
+ request.raw.on("close", cleanup);
329
332
  });
330
333
  app.get("/api/installations", async (_request, reply) => {
331
334
  return reply.send({ ok: true, installations: service.listLinearInstallations() });
package/dist/index.js CHANGED
@@ -62,15 +62,16 @@ async function main() {
62
62
  secrets: config.secretSources,
63
63
  }, "PatchRelay started");
64
64
  const shutdown = async () => {
65
- service.stop();
65
+ await service.stop();
66
66
  await app.close();
67
67
  };
68
- process.once("SIGINT", () => {
69
- void shutdown();
70
- });
71
- process.once("SIGTERM", () => {
72
- void shutdown();
73
- });
68
+ const onSignal = (signal) => {
69
+ shutdown().catch((error) => {
70
+ logger.error({ signal, error: error instanceof Error ? error.message : String(error) }, "Shutdown error");
71
+ }).finally(() => { process.exitCode ??= 0; });
72
+ };
73
+ process.once("SIGINT", () => onSignal("SIGINT"));
74
+ process.once("SIGTERM", () => onSignal("SIGTERM"));
74
75
  }
75
76
  main().catch((error) => {
76
77
  process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`);
@@ -44,10 +44,10 @@ export class ServiceRuntime {
44
44
  throw error;
45
45
  }
46
46
  }
47
- stop() {
47
+ async stop() {
48
48
  this.ready = false;
49
49
  this.clearBackgroundReconcile();
50
- void this.codex.stop();
50
+ await this.codex.stop();
51
51
  }
52
52
  enqueueWebhookEvent(eventId, options) {
53
53
  this.webhookQueue.enqueue(eventId, options);
package/dist/service.js CHANGED
@@ -115,9 +115,9 @@ export class PatchRelayService {
115
115
  await this.runtime.start();
116
116
  this.mergeQueue.seedOnStartup();
117
117
  }
118
- stop() {
118
+ async stop() {
119
119
  this.githubAppTokenManager?.stop();
120
- this.runtime.stop();
120
+ await this.runtime.stop();
121
121
  }
122
122
  async createLinearOAuthStart(params) {
123
123
  return await this.oauthService.createStart(params);
@@ -230,7 +230,8 @@ export class WebhookHandler {
230
230
  summary: `Delivered follow-up prompt to active ${activeRun.runType} workflow`,
231
231
  });
232
232
  }
233
- catch {
233
+ catch (error) {
234
+ this.logger.warn({ issueKey: trackedIssue?.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to deliver follow-up prompt");
234
235
  this.feed?.publish({
235
236
  level: "warn",
236
237
  kind: "agent",
@@ -288,7 +289,8 @@ export class WebhookHandler {
288
289
  summary: `Delivered follow-up comment to active ${run.runType} workflow`,
289
290
  });
290
291
  }
291
- catch {
292
+ catch (error) {
293
+ this.logger.warn({ issueKey: trackedIssue?.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to deliver follow-up comment");
292
294
  this.feed?.publish({
293
295
  level: "warn",
294
296
  kind: "comment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.12.7",
3
+ "version": "0.12.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {