patchrelay 0.10.5 → 0.10.7

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.10.5",
4
- "commit": "1ddec5e7bd28",
5
- "builtAt": "2026-03-22T19:39:50.538Z"
3
+ "version": "0.10.7",
4
+ "commit": "6593575e7ee8",
5
+ "builtAt": "2026-03-22T22:06:15.644Z"
6
6
  }
@@ -87,17 +87,29 @@ export class GitHubWebhookHandler {
87
87
  ...(event.reviewState !== undefined ? { prReviewState: event.reviewState } : {}),
88
88
  ...(event.checkStatus !== undefined ? { prCheckStatus: event.checkStatus } : {}),
89
89
  });
90
+ // Individual check_run events only update PR metadata, not factory state.
91
+ // State transitions and reactive runs are driven by check_suite completion
92
+ // to avoid flickering when multiple checks run in parallel.
93
+ const isIndividualCheckRun = event.eventSource === "check_run"
94
+ && (event.triggerEvent === "check_passed" || event.triggerEvent === "check_failed");
90
95
  // Drive factory state transitions from GitHub events
91
- const newState = resolveFactoryStateFromGitHub(event.triggerEvent, issue.factoryState);
92
- if (newState) {
93
- this.db.upsertIssue({
94
- projectId: issue.projectId,
95
- linearIssueId: issue.linearIssueId,
96
- factoryState: newState,
97
- });
98
- this.logger.info({ issueKey: issue.issueKey, from: issue.factoryState, to: newState, trigger: event.triggerEvent }, "Factory state transition from GitHub event");
99
- // Emit Linear activity for significant state changes
100
- void this.emitLinearActivity(issue, newState, event);
96
+ if (!isIndividualCheckRun) {
97
+ let newState = resolveFactoryStateFromGitHub(event.triggerEvent, issue.factoryState);
98
+ // Don't transition to failed on pr_closed when a run is active —
99
+ // Codex sometimes closes and reopens PRs during its workflow.
100
+ if (newState === "failed" && event.triggerEvent === "pr_closed" && issue.activeRunId !== undefined) {
101
+ newState = undefined;
102
+ }
103
+ if (newState && newState !== issue.factoryState) {
104
+ this.db.upsertIssue({
105
+ projectId: issue.projectId,
106
+ linearIssueId: issue.linearIssueId,
107
+ factoryState: newState,
108
+ });
109
+ this.logger.info({ issueKey: issue.issueKey, from: issue.factoryState, to: newState, trigger: event.triggerEvent }, "Factory state transition from GitHub event");
110
+ // Emit Linear activity for significant state changes
111
+ void this.emitLinearActivity(issue, newState, event);
112
+ }
101
113
  }
102
114
  // Reset repair counters on new push
103
115
  if (event.triggerEvent === "pr_synchronize") {
@@ -108,19 +120,23 @@ export class GitHubWebhookHandler {
108
120
  queueRepairAttempts: 0,
109
121
  });
110
122
  }
123
+ // Re-read issue after all upserts so reactive run logic sees current state
124
+ const freshIssue = this.db.getIssue(issue.projectId, issue.linearIssueId) ?? issue;
111
125
  this.logger.info({ issueKey: issue.issueKey, branchName: event.branchName, triggerEvent: event.triggerEvent, prNumber: event.prNumber }, "GitHub webhook: updated issue PR state");
112
126
  this.feed?.publish({
113
127
  level: event.triggerEvent.includes("failed") ? "warn" : "info",
114
128
  kind: "github",
115
- issueKey: issue.issueKey,
116
- projectId: issue.projectId,
117
- stage: issue.factoryState,
129
+ issueKey: freshIssue.issueKey,
130
+ projectId: freshIssue.projectId,
131
+ stage: freshIssue.factoryState,
118
132
  status: event.triggerEvent,
119
133
  summary: `GitHub: ${event.triggerEvent}${event.prNumber ? ` on PR #${event.prNumber}` : ""}`,
120
134
  detail: event.checkName ?? event.reviewBody?.slice(0, 200) ?? undefined,
121
135
  });
122
- // Trigger reactive runs if applicable
123
- this.maybeEnqueueReactiveRun(issue, event);
136
+ // Trigger reactive runs if applicable (skip individual check_run events)
137
+ if (!isIndividualCheckRun) {
138
+ this.maybeEnqueueReactiveRun(freshIssue, event);
139
+ }
124
140
  }
125
141
  maybeEnqueueReactiveRun(issue, event) {
126
142
  // Don't trigger if there's already an active run
@@ -119,6 +119,7 @@ function normalizeCheckSuiteEvent(payload, repoFullName) {
119
119
  headSha: suite.head_sha,
120
120
  prNumber: pr?.number,
121
121
  checkStatus: passed ? "success" : "failure",
122
+ eventSource: "check_suite",
122
123
  };
123
124
  }
124
125
  function normalizeCheckRunEvent(payload, repoFullName) {
@@ -140,6 +141,7 @@ function normalizeCheckRunEvent(payload, repoFullName) {
140
141
  checkStatus: passed ? "success" : "failure",
141
142
  checkName: run.name,
142
143
  checkUrl: run.html_url,
144
+ eventSource: "check_run",
143
145
  };
144
146
  }
145
147
  function normalizeMergeGroupEvent(payload, repoFullName) {
@@ -30,7 +30,7 @@ function buildRunPrompt(issue, runType, repoPath, context) {
30
30
  const lines = [
31
31
  `Issue: ${issue.issueKey ?? issue.linearIssueId}`,
32
32
  issue.title ? `Title: ${issue.title}` : undefined,
33
- `Branch: ${issue.branchName}`,
33
+ issue.branchName ? `Branch: ${issue.branchName}` : undefined,
34
34
  issue.prNumber ? `PR: #${issue.prNumber}` : undefined,
35
35
  "",
36
36
  ].filter(Boolean);
@@ -161,8 +161,9 @@ export class RunOrchestrator {
161
161
  if (prepareResult.ran && prepareResult.exitCode !== 0) {
162
162
  throw new Error(`prepare-worktree hook failed (exit ${prepareResult.exitCode}): ${prepareResult.stderr?.slice(0, 500) ?? ""}`);
163
163
  }
164
- // Start or reuse Codex thread
165
- if (issue.threadId && runType !== "implementation") {
164
+ // Reuse the existing thread only for review_fix (reviewer context matters).
165
+ // Implementation, ci_repair, and queue_repair get fresh threads.
166
+ if (issue.threadId && runType === "review_fix") {
166
167
  threadId = issue.threadId;
167
168
  }
168
169
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {