patchrelay 0.12.4 → 0.12.5
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/dist/build-info.json +3 -3
- package/dist/db.js +1 -1
- package/dist/run-orchestrator.js +39 -14
- package/dist/service.js +14 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/db.js
CHANGED
|
@@ -260,7 +260,7 @@ export class PatchRelayDatabase {
|
|
|
260
260
|
}
|
|
261
261
|
listRunningRuns() {
|
|
262
262
|
const rows = this.connection
|
|
263
|
-
.prepare("SELECT * FROM runs WHERE status
|
|
263
|
+
.prepare("SELECT * FROM runs WHERE status IN ('running', 'queued')")
|
|
264
264
|
.all();
|
|
265
265
|
return rows.map(mapRunRow);
|
|
266
266
|
}
|
package/dist/run-orchestrator.js
CHANGED
|
@@ -328,24 +328,49 @@ export class RunOrchestrator {
|
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
async reconcileRun(run) {
|
|
331
|
-
if (!run.threadId) {
|
|
332
|
-
this.failRunAndClear(run, "Run has no thread ID during reconciliation");
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
331
|
const issue = this.db.getIssue(run.projectId, run.linearIssueId);
|
|
336
332
|
if (!issue)
|
|
337
333
|
return;
|
|
338
|
-
//
|
|
334
|
+
// Zombie run: claimed in DB but Codex never started (no thread).
|
|
335
|
+
// This happens when the service crashes between claiming the run
|
|
336
|
+
// and starting the Codex turn. Re-enqueue instead of failing.
|
|
337
|
+
if (!run.threadId) {
|
|
338
|
+
this.logger.warn({ issueKey: issue.issueKey, runId: run.id, runType: run.runType }, "Zombie run detected (no thread) — clearing and re-enqueueing");
|
|
339
|
+
this.db.transaction(() => {
|
|
340
|
+
this.db.finishRun(run.id, { status: "failed", failureReason: "Zombie: never started (no thread after restart)" });
|
|
341
|
+
this.db.upsertIssue({
|
|
342
|
+
projectId: run.projectId,
|
|
343
|
+
linearIssueId: run.linearIssueId,
|
|
344
|
+
activeRunId: null,
|
|
345
|
+
pendingRunType: run.runType,
|
|
346
|
+
pendingRunContextJson: null,
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
this.enqueueIssue(run.projectId, run.linearIssueId);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// Read Codex state — thread may not exist after app-server restart.
|
|
339
353
|
let thread;
|
|
340
354
|
try {
|
|
341
355
|
thread = await this.readThreadWithRetry(run.threadId);
|
|
342
356
|
}
|
|
343
357
|
catch {
|
|
344
|
-
this.
|
|
358
|
+
this.logger.warn({ issueKey: issue.issueKey, runId: run.id, runType: run.runType, threadId: run.threadId }, "Stale thread during reconciliation — clearing and re-enqueueing");
|
|
359
|
+
this.db.transaction(() => {
|
|
360
|
+
this.db.finishRun(run.id, { status: "failed", failureReason: "Stale thread after restart" });
|
|
361
|
+
this.db.upsertIssue({
|
|
362
|
+
projectId: run.projectId,
|
|
363
|
+
linearIssueId: run.linearIssueId,
|
|
364
|
+
activeRunId: null,
|
|
365
|
+
pendingRunType: run.runType,
|
|
366
|
+
pendingRunContextJson: null,
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
this.enqueueIssue(run.projectId, run.linearIssueId);
|
|
345
370
|
return;
|
|
346
371
|
}
|
|
347
|
-
// Check Linear state
|
|
348
|
-
const linear = await this.linearProvider.forProject(run.projectId);
|
|
372
|
+
// Check Linear state (non-fatal — token refresh may fail)
|
|
373
|
+
const linear = await this.linearProvider.forProject(run.projectId).catch(() => undefined);
|
|
349
374
|
if (linear) {
|
|
350
375
|
const linearIssue = await linear.getIssue(run.linearIssueId).catch(() => undefined);
|
|
351
376
|
if (linearIssue) {
|
|
@@ -429,10 +454,10 @@ export class RunOrchestrator {
|
|
|
429
454
|
async emitLinearActivity(issue, type, body, options) {
|
|
430
455
|
if (!issue.agentSessionId)
|
|
431
456
|
return;
|
|
432
|
-
const linear = await this.linearProvider.forProject(issue.projectId);
|
|
433
|
-
if (!linear)
|
|
434
|
-
return;
|
|
435
457
|
try {
|
|
458
|
+
const linear = await this.linearProvider.forProject(issue.projectId);
|
|
459
|
+
if (!linear)
|
|
460
|
+
return;
|
|
436
461
|
await linear.createAgentActivity({
|
|
437
462
|
agentSessionId: issue.agentSessionId,
|
|
438
463
|
content: { type, body },
|
|
@@ -446,10 +471,10 @@ export class RunOrchestrator {
|
|
|
446
471
|
async updateLinearPlan(issue, plan) {
|
|
447
472
|
if (!issue.agentSessionId)
|
|
448
473
|
return;
|
|
449
|
-
const linear = await this.linearProvider.forProject(issue.projectId);
|
|
450
|
-
if (!linear?.updateAgentSession)
|
|
451
|
-
return;
|
|
452
474
|
try {
|
|
475
|
+
const linear = await this.linearProvider.forProject(issue.projectId);
|
|
476
|
+
if (!linear?.updateAgentSession)
|
|
477
|
+
return;
|
|
453
478
|
await linear.updateAgentSession({ agentSessionId: issue.agentSessionId, plan });
|
|
454
479
|
}
|
|
455
480
|
catch (error) {
|
package/dist/service.js
CHANGED
|
@@ -77,6 +77,20 @@ export class PatchRelayService {
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
async start() {
|
|
80
|
+
// Verify Linear connectivity for all configured projects before starting.
|
|
81
|
+
// Fail fast on auth errors rather than crashing mid-run.
|
|
82
|
+
for (const project of this.config.projects) {
|
|
83
|
+
try {
|
|
84
|
+
const client = await this.linearProvider.forProject(project.id);
|
|
85
|
+
if (!client) {
|
|
86
|
+
this.logger.warn({ projectId: project.id }, "No Linear installation linked — project will not receive agent session events");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
91
|
+
throw new Error(`Linear auth failed for project ${project.id}: ${msg}. Re-run "patchrelay connect" to refresh the token.`, { cause: error });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
80
94
|
if (this.githubAppTokenManager) {
|
|
81
95
|
await ensureGhWrapper(this.logger);
|
|
82
96
|
await this.githubAppTokenManager.start();
|