patchrelay 0.41.6 → 0.41.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.41.6",
4
- "commit": "24441e13272f",
5
- "builtAt": "2026-04-13T17:33:37.051Z"
3
+ "version": "0.41.7",
4
+ "commit": "604350d6627f",
5
+ "builtAt": "2026-04-13T18:24:22.746Z"
6
6
  }
@@ -1,6 +1,17 @@
1
1
  import { existsSync, lstatSync, realpathSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { ensureDir, execCommand } from "./utils.js";
4
+ const FETCH_RETRY_DELAYS_MS = [0, 1_000];
5
+ const TRANSIENT_FETCH_ERROR_PATTERNS = [
6
+ /connection reset by peer/i,
7
+ /recv failure/i,
8
+ /tls handshake timeout/i,
9
+ /connection timed out/i,
10
+ /operation timed out/i,
11
+ /timed out after \d+ms/i,
12
+ /remote end hung up unexpectedly/i,
13
+ /unexpected disconnect while reading sideband packet/i,
14
+ ];
4
15
  export class WorktreeManager {
5
16
  config;
6
17
  constructor(config) {
@@ -84,9 +95,7 @@ export class WorktreeManager {
84
95
  // Fetch latest main so the branch forks from a clean, up-to-date base.
85
96
  // This prevents branch contamination when local HEAD has drifted.
86
97
  // freshenWorktree in run-orchestrator acts as a secondary safety net.
87
- const fetchResult = await execCommand(this.config.runner.gitBin, ["-C", repoPath, "fetch", "origin", "main"], {
88
- timeoutMs: 60_000,
89
- });
98
+ const fetchResult = await this.fetchWithTransientRetry(repoPath, "main");
90
99
  if (fetchResult.exitCode !== 0) {
91
100
  throw new Error(`Failed to fetch origin/main before creating issue worktree: ${fetchResult.stderr?.slice(0, 300) ?? "git fetch failed"}`);
92
101
  }
@@ -95,6 +104,34 @@ export class WorktreeManager {
95
104
  throw new Error(`Failed to create issue worktree at ${worktreePath}: ${addResult.stderr?.slice(0, 300) ?? "git worktree add failed"}`);
96
105
  }
97
106
  }
107
+ async fetchWithTransientRetry(repoPath, branchName) {
108
+ let lastResult;
109
+ let lastError;
110
+ for (let attempt = 0; attempt <= FETCH_RETRY_DELAYS_MS.length; attempt += 1) {
111
+ if (attempt > 0) {
112
+ await delay(FETCH_RETRY_DELAYS_MS[attempt - 1] ?? 0);
113
+ }
114
+ try {
115
+ const result = await execCommand(this.config.runner.gitBin, ["-C", repoPath, "fetch", "origin", branchName], {
116
+ timeoutMs: 60_000,
117
+ });
118
+ if (result.exitCode === 0 || !isTransientFetchFailure(result.stderr)) {
119
+ return result;
120
+ }
121
+ lastResult = result;
122
+ }
123
+ catch (error) {
124
+ if (!isTransientFetchFailure(error instanceof Error ? error.message : String(error))) {
125
+ throw error;
126
+ }
127
+ lastError = error instanceof Error ? error : new Error(String(error));
128
+ }
129
+ }
130
+ if (lastResult) {
131
+ return lastResult;
132
+ }
133
+ throw lastError ?? new Error(`Failed to fetch origin/${branchName}`);
134
+ }
98
135
  async assertTrustedExistingWorktree(repoPath, worktreeRoot, worktreePath, options) {
99
136
  const worktreeStats = lstatSync(worktreePath);
100
137
  if (worktreeStats.isSymbolicLink()) {
@@ -145,3 +182,15 @@ function isPathWithinRoot(rootPath, candidatePath) {
145
182
  const relative = path.relative(rootPath, candidatePath);
146
183
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
147
184
  }
185
+ function isTransientFetchFailure(message) {
186
+ if (!message) {
187
+ return false;
188
+ }
189
+ return TRANSIENT_FETCH_ERROR_PATTERNS.some((pattern) => pattern.test(message));
190
+ }
191
+ function delay(delayMs) {
192
+ if (delayMs <= 0) {
193
+ return Promise.resolve();
194
+ }
195
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
196
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.41.6",
3
+ "version": "0.41.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {