@link-assistant/hive-mind 1.31.1 → 1.31.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.31.2
4
+
5
+ ### Patch Changes
6
+
7
+ - efe3506: fix: /merge command no longer falsely fails when latest CI is in progress (Issue #1425)
8
+
9
+ The `checkBranchCIHealth` function previously queried only `status=completed` runs
10
+ to determine if the default branch CI was healthy. When a new commit had an in-progress
11
+ CI run, the function returned the previous (now superseded) commit's failure as the
12
+ "latest" CI status, causing the merge queue to be blocked with a false positive error.
13
+
14
+ The fix resolves the actual HEAD SHA of the branch first, then queries CI runs
15
+ specifically for that SHA (without a status filter). If the latest commit's runs are
16
+ in progress, the function returns `pending: true` (healthy) instead of reporting a
17
+ failure from an older commit. The merge queue then proceeds to the existing
18
+ `waitForTargetBranchCI` step which correctly waits for those runs to complete.
19
+
3
20
  ## 1.31.1
4
21
 
5
22
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.31.1",
3
+ "version": "1.31.2",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -141,62 +141,105 @@ export async function waitForCommitCI(owner, repo, sha, options = {}, verbose =
141
141
  /**
142
142
  * Check if the default branch has any recent failed CI runs
143
143
  * Issue #1341: Used to detect pre-existing failures before starting the merge queue
144
+ * Issue #1425: Fixed to resolve the actual HEAD SHA first, then check CI for that SHA,
145
+ * so that in-progress runs on the latest commit are not mistaken for failures.
144
146
  *
145
147
  * @param {string} owner - Repository owner
146
148
  * @param {string} repo - Repository name
147
149
  * @param {string} branch - Branch name (usually 'main' or 'master')
148
- * @param {Object} options - Check options
149
- * @param {number} options.lookbackCount - Number of recent runs to check (default: 5)
150
+ * @param {Object} options - Check options (currently unused, kept for API compatibility)
150
151
  * @param {boolean} verbose - Whether to log verbose output
151
- * @returns {Promise<{healthy: boolean, failedRuns: Array, error: string|null}>}
152
+ * @returns {Promise<{healthy: boolean, pending: boolean, failedRuns: Array, pendingRuns: Array, error: string|null}>}
152
153
  */
153
- export async function checkBranchCIHealth(owner, repo, branch = 'main', options = {}, verbose = false) {
154
- const { lookbackCount = 5 } = options;
155
-
154
+ export async function checkBranchCIHealth(owner, repo, branch = 'main', options, verbose = false) {
156
155
  try {
157
- // Get recent completed workflow runs on the branch
158
- const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?branch=${branch}&status=completed&per_page=${lookbackCount}" --jq '[.workflow_runs[] | {id: .id, name: .name, status: .status, conclusion: .conclusion, html_url: .html_url, head_sha: .head_sha, created_at: .created_at}]'`);
156
+ // Issue #1425: First, resolve the actual HEAD SHA of the branch.
157
+ // This avoids the bug where only completed runs are queried: if the latest commit has
158
+ // an in-progress CI run, querying ?status=completed would return the previous commit's
159
+ // runs and could incorrectly report a failure from an older (now superseded) commit.
160
+ let headSha;
161
+ try {
162
+ const { stdout: refOut } = await exec(`gh api "repos/${owner}/${repo}/git/ref/heads/${branch}" --jq '.object.sha'`);
163
+ headSha = refOut.trim();
164
+ } catch (refError) {
165
+ if (verbose) {
166
+ console.log(`[VERBOSE] /merge: Error resolving HEAD SHA for ${branch}: ${refError.message}`);
167
+ }
168
+ // On error, assume healthy to avoid blocking merges due to API issues
169
+ return { healthy: true, pending: false, failedRuns: [], pendingRuns: [], error: null };
170
+ }
171
+
172
+ if (!headSha) {
173
+ if (verbose) {
174
+ console.log(`[VERBOSE] /merge: Could not resolve HEAD SHA for ${branch}, assuming healthy`);
175
+ }
176
+ return { healthy: true, pending: false, failedRuns: [], pendingRuns: [], error: null };
177
+ }
178
+
179
+ if (verbose) {
180
+ console.log(`[VERBOSE] /merge: Checking CI for latest ${branch} commit ${headSha.substring(0, 7)}`);
181
+ }
182
+
183
+ // Issue #1425: Query CI runs specifically for the HEAD SHA (no status filter).
184
+ // This ensures we see in-progress runs for the latest commit, not just completed ones.
185
+ const { stdout } = await exec(`gh api "repos/${owner}/${repo}/actions/runs?head_sha=${headSha}&per_page=20" --jq '[.workflow_runs[] | {id: .id, name: .name, status: .status, conclusion: .conclusion, html_url: .html_url, head_sha: .head_sha, created_at: .created_at}]'`);
159
186
  const runs = JSON.parse(stdout.trim() || '[]');
160
187
 
161
188
  if (verbose) {
162
- console.log(`[VERBOSE] /merge: Checking ${runs.length} recent CI runs on ${owner}/${repo} branch ${branch}`);
189
+ console.log(`[VERBOSE] /merge: Found ${runs.length} CI run(s) for HEAD commit ${headSha.substring(0, 7)} on ${owner}/${repo} branch ${branch}`);
163
190
  }
164
191
 
165
192
  if (runs.length === 0) {
166
- // No recent runs - assume healthy
167
- return { healthy: true, failedRuns: [], error: null };
193
+ // No runs for the latest commit - CI may not have started yet or is not configured.
194
+ // Assume healthy to avoid blocking merges.
195
+ return { healthy: true, pending: false, failedRuns: [], pendingRuns: [], error: null };
196
+ }
197
+
198
+ // Issue #1425: Check for in-progress runs on the latest commit.
199
+ // If the latest commit's CI is still running, we should NOT report failure —
200
+ // the previous commit's failure (which may appear in completed runs) is no longer relevant.
201
+ const pendingRuns = runs.filter(r => r.status === 'in_progress' || r.status === 'queued' || r.status === 'waiting' || r.status === 'requested' || r.status === 'pending');
202
+ if (pendingRuns.length > 0) {
203
+ if (verbose) {
204
+ console.log(`[VERBOSE] /merge: ${pendingRuns.length} CI run(s) still in progress on ${branch} (latest commit ${headSha.substring(0, 7)})`);
205
+ for (const run of pendingRuns) {
206
+ console.log(`[VERBOSE] /merge: - ${run.name}: ${run.status} (${run.html_url})`);
207
+ }
208
+ }
209
+ // Healthy but pending: caller should wait for CI rather than block the queue
210
+ return { healthy: true, pending: true, failedRuns: [], pendingRuns, error: null };
168
211
  }
169
212
 
170
- // Check for failures in the most recent run(s)
171
- const latestSha = runs[0].head_sha;
172
- const latestRuns = runs.filter(r => r.head_sha === latestSha);
173
- const failedRuns = latestRuns.filter(r => r.conclusion === 'failure' || r.conclusion === 'timed_out');
213
+ // All runs for the latest commit are completed — check for failures
214
+ const failedRuns = runs.filter(r => r.conclusion === 'failure' || r.conclusion === 'timed_out');
174
215
 
175
216
  if (failedRuns.length > 0) {
176
217
  if (verbose) {
177
- console.log(`[VERBOSE] /merge: Found ${failedRuns.length} failed CI run(s) on ${branch}:`);
218
+ console.log(`[VERBOSE] /merge: Found ${failedRuns.length} failed CI run(s) on ${branch} (latest commit ${headSha.substring(0, 7)}):`);
178
219
  for (const run of failedRuns) {
179
220
  console.log(`[VERBOSE] /merge: - ${run.name}: ${run.conclusion} (${run.html_url})`);
180
221
  }
181
222
  }
182
223
  return {
183
224
  healthy: false,
225
+ pending: false,
184
226
  failedRuns,
227
+ pendingRuns: [],
185
228
  error: `${failedRuns.length} CI run(s) failed on ${branch}: ${failedRuns.map(r => r.name).join(', ')}`,
186
229
  };
187
230
  }
188
231
 
189
232
  if (verbose) {
190
- console.log(`[VERBOSE] /merge: Branch ${branch} CI is healthy (${latestRuns.length} runs checked)`);
233
+ console.log(`[VERBOSE] /merge: Branch ${branch} CI is healthy (${runs.length} run(s) passed for commit ${headSha.substring(0, 7)})`);
191
234
  }
192
235
 
193
- return { healthy: true, failedRuns: [], error: null };
236
+ return { healthy: true, pending: false, failedRuns: [], pendingRuns: [], error: null };
194
237
  } catch (error) {
195
238
  if (verbose) {
196
239
  console.log(`[VERBOSE] /merge: Error checking branch CI health: ${error.message}`);
197
240
  }
198
241
  // On error, assume healthy to avoid blocking merges due to API issues
199
- return { healthy: true, failedRuns: [], error: null };
242
+ return { healthy: true, pending: false, failedRuns: [], pendingRuns: [], error: null };
200
243
  }
201
244
  }
202
245
 
@@ -552,6 +552,19 @@ export class MergeQueueProcessor {
552
552
  };
553
553
  }
554
554
 
555
+ // Issue #1425: If the latest commit's CI is still in progress, wait for it to complete
556
+ // rather than proceeding immediately. The WAIT_FOR_TARGET_BRANCH_CI step (below) will
557
+ // also wait, but checking here ensures we don't skip the health check entirely.
558
+ if (healthResult.pending) {
559
+ this.log(`Branch ${targetBranch} has ${healthResult.pendingRuns.length} CI run(s) in progress on the latest commit. Will wait for them to complete.`);
560
+ // Return healthy so the queue proceeds to the waitForTargetBranchCI step which handles waiting
561
+ return {
562
+ healthy: true,
563
+ failedRuns: [],
564
+ error: null,
565
+ };
566
+ }
567
+
555
568
  this.log(`Branch ${targetBranch} CI is healthy. Ready to proceed.`);
556
569
  return {
557
570
  healthy: true,