@haystackeditor/cli 0.14.4 → 0.14.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.
@@ -356,8 +356,17 @@ class GitHubWriteError extends Error {
356
356
  * the file, and surfaces a real error if the write itself fails. */
357
357
  async function fetchFileSha(owner, repo, path, token) {
358
358
  const res = await fetch(`${GITHUB_PROXY}/repos/${owner}/${repo}/contents/${path}`, { headers: ghHeaders(token) });
359
- if (!res.ok)
359
+ if (!res.ok) {
360
+ if (res.status !== 404) {
361
+ trackError('haystack_setup_fetch_file_sha_failed', {
362
+ owner,
363
+ repo,
364
+ path,
365
+ status: res.status,
366
+ });
367
+ }
360
368
  return null;
369
+ }
361
370
  const data = (await res.json());
362
371
  return data.sha ?? null;
363
372
  }
@@ -463,9 +472,11 @@ async function writeOnboardingFilesToRepo(owner, repo, files, autoMergeEnabled,
463
472
  for (const f of files) {
464
473
  shaByPath.set(f.path, await fetchFileSha(owner, repo, f.path, token));
465
474
  }
475
+ let successfulWrites = 0;
466
476
  try {
467
477
  for (const f of files) {
468
478
  await putFile(owner, repo, f.path, f.content, f.message, shaByPath.get(f.path) ?? null, token);
479
+ successfulWrites++;
469
480
  }
470
481
  return {};
471
482
  }
@@ -475,6 +486,9 @@ async function writeOnboardingFilesToRepo(owner, repo, files, autoMergeEnabled,
475
486
  const status = err instanceof GitHubWriteError ? err.status : 0;
476
487
  if (status !== 409 && status !== 422)
477
488
  throw err;
489
+ if (successfulWrites > 0) {
490
+ throw err;
491
+ }
478
492
  // Probe GitHub's *structured* protection signals to decide: protection
479
493
  // (→ PR fallback) vs. a genuine bad request / sha conflict (→ surface the
480
494
  // error). This replaces brittle error-message substring matching.
@@ -514,6 +528,8 @@ async function runUnifiedScan(repo, token) {
514
528
  if (!runId)
515
529
  throw new Error('Scan kickoff returned no runId');
516
530
  let lastProgressKey = '';
531
+ let loggedPollFetchError = false;
532
+ let loggedPollStatusError = false;
517
533
  // eslint-disable-next-line no-constant-condition
518
534
  while (true) {
519
535
  await new Promise((resolve) => setTimeout(resolve, SCAN_POLL_INTERVAL_MS));
@@ -523,14 +539,14 @@ async function runUnifiedScan(repo, token) {
523
539
  }
524
540
  catch (err) {
525
541
  // Transient network blip — the KV state is authoritative and won't vanish
526
- // from one failed poll, so keep going. Record it (non-disruptive, no
527
- // terminal noise) so a persistently-failing poll isn't invisible
528
- // (PR001: no silent failure).
529
- trackError('onboarding_scan_poll_network_error', {
530
- repo,
531
- run_id: runId,
532
- error_message: err instanceof Error ? err.message : String(err),
533
- });
542
+ // from one failed poll, so keep going.
543
+ if (!loggedPollFetchError) {
544
+ loggedPollFetchError = true;
545
+ trackError('haystack_setup_scan_status_poll_fetch_error', {
546
+ repo,
547
+ error: err instanceof Error ? err.message : String(err),
548
+ });
549
+ }
534
550
  continue;
535
551
  }
536
552
  if (statusResp.status === 404) {
@@ -540,6 +556,13 @@ async function runUnifiedScan(repo, token) {
540
556
  }
541
557
  if (!statusResp.ok) {
542
558
  // Transient (5xx etc.) — back off and retry on the next tick.
559
+ if (!loggedPollStatusError) {
560
+ loggedPollStatusError = true;
561
+ trackError('haystack_setup_scan_status_poll_non_ok', {
562
+ repo,
563
+ status: statusResp.status,
564
+ });
565
+ }
543
566
  continue;
544
567
  }
545
568
  const state = (await statusResp.json());
@@ -84,7 +84,7 @@ async function githubApiRequest(method, path, token, body) {
84
84
  // (e.g. the App isn't installed on this owner), fall back to the original
85
85
  // response so handleApiError surfaces GitHub's actual "org restricted" 403.
86
86
  const proxied = await fetch(`${GITHUB_PROXY_BASE}${path}`, init);
87
- return proxied.ok ? proxied : direct;
87
+ return proxied.status === 403 ? direct : proxied;
88
88
  }
89
89
  /**
90
90
  * Handle API errors consistently
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.14.4",
3
+ "version": "0.14.5",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {