@boxes-dev/dvb 0.2.46 → 0.2.48

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 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/devbox/commands/init/index.ts"],"names":[],"mappings":"AAwfA,eAAO,MAAM,OAAO,GAAU,MAAM,MAAM,EAAE,kBA2gF3C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/devbox/commands/init/index.ts"],"names":[],"mappings":"AA0mBA,eAAO,MAAM,OAAO,GAAU,MAAM,MAAM,EAAE,kBA89E3C,CAAC"}
@@ -42,6 +42,93 @@ const ensurePrivateDir = async (dir) => {
42
42
  // best effort on filesystems that do not support chmod
43
43
  }
44
44
  };
45
+ const DEFAULT_INIT_STEP_RETRIES = 3;
46
+ const INIT_STEP_RETRYABLE_STATUSES = new Set([
47
+ 408, 409, 425, 429, 500, 502, 503, 504,
48
+ ]);
49
+ const delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
50
+ const computeRetryDelayMs = (retryIndex) => {
51
+ // retryIndex is 1-based (1..N)
52
+ const base = 500;
53
+ const cap = 5000;
54
+ const exp = Math.min(cap, base * 2 ** Math.max(0, retryIndex - 1));
55
+ const jitter = Math.floor(Math.random() * 150);
56
+ return exp + jitter;
57
+ };
58
+ const getErrorCode = (error) => {
59
+ if (!error || typeof error !== "object")
60
+ return undefined;
61
+ const record = error;
62
+ const code = record.code;
63
+ return typeof code === "string" || typeof code === "number"
64
+ ? String(code)
65
+ : undefined;
66
+ };
67
+ const parseExecUnexpectedResponseStatus = (error) => {
68
+ if (!(error instanceof Error))
69
+ return null;
70
+ const match = /Unexpected server response: (\d{3})/.exec(error.message);
71
+ if (!match)
72
+ return null;
73
+ const status = Number(match[1]);
74
+ return Number.isFinite(status) ? status : null;
75
+ };
76
+ const isRetryableInitError = (error) => {
77
+ if (error instanceof SpritesApiError) {
78
+ return INIT_STEP_RETRYABLE_STATUSES.has(error.status);
79
+ }
80
+ // Node's fetch() throws TypeError for network failures.
81
+ if (error instanceof TypeError)
82
+ return true;
83
+ if (error instanceof Error &&
84
+ (error.name === "AbortError" || error.message.includes("aborted"))) {
85
+ return true;
86
+ }
87
+ const execStatus = parseExecUnexpectedResponseStatus(error);
88
+ if (execStatus !== null) {
89
+ return INIT_STEP_RETRYABLE_STATUSES.has(execStatus);
90
+ }
91
+ const code = getErrorCode(error);
92
+ return (code === "ECONNRESET" ||
93
+ code === "ETIMEDOUT" ||
94
+ code === "EAI_AGAIN" ||
95
+ code === "ENOTFOUND" ||
96
+ code === "ECONNREFUSED");
97
+ };
98
+ const retryInitStep = async ({ status, title, retries = DEFAULT_INIT_STEP_RETRIES, shouldRetry = isRetryableInitError, fn, }) => {
99
+ let lastError = null;
100
+ const maxRetries = Math.max(0, Math.floor(retries));
101
+ const attempts = maxRetries + 1;
102
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
103
+ try {
104
+ return await fn();
105
+ }
106
+ catch (error) {
107
+ lastError = error;
108
+ const retryIndex = attempt; // 1-based retry count for the next attempt
109
+ if (retryIndex > maxRetries || !shouldRetry(error)) {
110
+ throw error;
111
+ }
112
+ status.stage(`${title} (retry ${retryIndex}/${maxRetries})`);
113
+ logger.warn("init_step_retry", {
114
+ title,
115
+ retry: retryIndex,
116
+ retries: maxRetries,
117
+ errorName: error instanceof Error ? error.name : undefined,
118
+ errorMessage: error instanceof Error ? error.message : String(error),
119
+ errorCode: getErrorCode(error),
120
+ ...(error instanceof SpritesApiError ? { status: error.status } : {}),
121
+ ...(parseExecUnexpectedResponseStatus(error) !== null
122
+ ? { execStatus: parseExecUnexpectedResponseStatus(error) }
123
+ : {}),
124
+ });
125
+ await delay(computeRetryDelayMs(retryIndex));
126
+ }
127
+ }
128
+ throw lastError instanceof Error
129
+ ? lastError
130
+ : new Error(`Init step failed: ${title}`);
131
+ };
45
132
  const migrateLegacyRepoDevboxDir = async ({ repoRoot, projectDir, }) => {
46
133
  const legacyDir = path.join(repoRoot, ".devbox");
47
134
  try {
@@ -210,14 +297,26 @@ const toRepoRelativePath = (repoRoot, filePath) => {
210
297
  return toPosixPath(relative);
211
298
  };
212
299
  const isOutsideRepoPath = (relativePath) => relativePath === ".." || relativePath.startsWith(`..${path.posix.sep}`);
213
- const ensureWorkdirOwnership = async ({ client, canonical, workdir, status, json, }) => {
300
+ const ensureWorkdirOwnership = async ({ client, canonical, workdir, status, }) => {
214
301
  const checkResult = await client.exec(canonical, [
215
302
  "/bin/bash",
216
303
  "-lc",
217
- `if [ -d ${shellQuote(workdir)} ]; then stat -c %U ${shellQuote(workdir)}; fi`,
304
+ [
305
+ "set -euo pipefail",
306
+ `dir=${shellQuote(workdir)}`,
307
+ 'if [ ! -d "$dir" ]; then',
308
+ ' echo "Missing workdir: $dir" >&2',
309
+ " exit 1",
310
+ "fi",
311
+ 'stat -c %U "$dir"',
312
+ ].join("\n"),
218
313
  ]);
219
- if (checkResult.exitCode !== 0)
220
- return;
314
+ if (checkResult.exitCode !== 0) {
315
+ const details = checkResult.stderr || checkResult.stdout || "";
316
+ throw new Error(details
317
+ ? `Failed to check workdir ownership: ${details.trim()}`
318
+ : `Failed to check workdir ownership (exit ${checkResult.exitCode})`);
319
+ }
221
320
  const owner = checkResult.stdout.trim();
222
321
  if (!owner || owner === "sprite")
223
322
  return;
@@ -228,15 +327,10 @@ const ensureWorkdirOwnership = async ({ client, canonical, workdir, status, json
228
327
  `sudo -n chown -R sprite:sprite ${shellQuote(workdir)}`,
229
328
  ]);
230
329
  if (chownResult.exitCode !== 0) {
231
- logger.warn("workdir_chown_failed", {
232
- box: canonical,
233
- workdir,
234
- error: chownResult.stderr || chownResult.stdout,
235
- });
236
- if (!json) {
237
- status.stop();
238
- console.warn("Warning: failed to update workdir ownership. Git may refuse to operate until ownership is fixed.");
239
- }
330
+ const details = chownResult.stderr || chownResult.stdout || "";
331
+ throw new Error(details
332
+ ? `Failed to update workdir ownership: ${details.trim()}`
333
+ : `Failed to update workdir ownership (exit ${chownResult.exitCode})`);
240
334
  }
241
335
  };
242
336
  const ensureGitSafeDirectory = async ({ client, canonical, workdir, status, }) => {
@@ -573,7 +667,7 @@ export const runInit = async (args) => {
573
667
  fingerprint,
574
668
  phase: label,
575
669
  });
576
- return { id: null, comment, createdAt };
670
+ throw new Error("Checkpoint ID missing.");
577
671
  }
578
672
  const record = { id, comment, createdAt };
579
673
  if (initState) {
@@ -738,13 +832,17 @@ export const runInit = async (args) => {
738
832
  });
739
833
  await runInitStep({
740
834
  enabled: progressEnabled,
741
- title: "Snapshotting filesystem (pre-setup) (optional)",
742
- fn: async ({ fail, ok }) => {
835
+ title: "Snapshotting filesystem (pre-setup)",
836
+ fn: async ({ status, fail, ok }) => {
743
837
  try {
744
- const checkpoint = await recordCodexCheckpoint({
745
- client,
746
- canonical,
747
- phase: "preCodexSetup",
838
+ const checkpoint = await retryInitStep({
839
+ status,
840
+ title: "Snapshotting filesystem (pre-setup)",
841
+ fn: async () => await recordCodexCheckpoint({
842
+ client,
843
+ canonical,
844
+ phase: "preCodexSetup",
845
+ }),
748
846
  });
749
847
  if (checkpoint.id) {
750
848
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -757,10 +855,8 @@ export const runInit = async (args) => {
757
855
  phase: "pre-codex-setup",
758
856
  error: error instanceof Error ? error.message : String(error),
759
857
  });
760
- fail("Snapshotting filesystem (pre-setup) (optional) (failed)");
761
- if (!parsed.json) {
762
- console.warn("Warning: failed to create pre-setup filesystem snapshot. Continuing without it.");
763
- }
858
+ fail("Snapshotting filesystem (pre-setup) (failed)");
859
+ throw error;
764
860
  }
765
861
  },
766
862
  });
@@ -796,13 +892,17 @@ export const runInit = async (args) => {
796
892
  });
797
893
  await runInitStep({
798
894
  enabled: progressEnabled,
799
- title: "Snapshotting filesystem (post-setup) (optional)",
800
- fn: async ({ fail, ok }) => {
895
+ title: "Snapshotting filesystem (post-setup)",
896
+ fn: async ({ status, fail, ok }) => {
801
897
  try {
802
- const checkpoint = await recordCodexCheckpoint({
803
- client,
804
- canonical,
805
- phase: "postCodexSetup",
898
+ const checkpoint = await retryInitStep({
899
+ status,
900
+ title: "Snapshotting filesystem (post-setup)",
901
+ fn: async () => await recordCodexCheckpoint({
902
+ client,
903
+ canonical,
904
+ phase: "postCodexSetup",
905
+ }),
806
906
  });
807
907
  if (checkpoint.id) {
808
908
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -815,10 +915,8 @@ export const runInit = async (args) => {
815
915
  phase: "post-codex-setup",
816
916
  error: error instanceof Error ? error.message : String(error),
817
917
  });
818
- fail("Snapshotting filesystem (post-setup) (optional) (failed)");
819
- if (!parsed.json) {
820
- console.warn("Warning: failed to create post-setup filesystem snapshot. Continuing without it.");
821
- }
918
+ fail("Snapshotting filesystem (post-setup) (failed)");
919
+ throw error;
822
920
  }
823
921
  },
824
922
  });
@@ -956,25 +1054,20 @@ export const runInit = async (args) => {
956
1054
  localPaths: projectLocalPaths,
957
1055
  });
958
1056
  const updateRegistryProjectStatus = async (initStatus) => {
959
- try {
960
- await requestJson(socketInfo.socketPath, "POST", "/registry/upsert", DAEMON_TIMEOUT_MS.registry, {
961
- project: buildProjectEntry(initStatus),
962
- });
963
- }
964
- catch (error) {
965
- logger.warn("registry_project_update_failed", {
966
- box: canonical,
967
- status: initStatus,
968
- error: String(error),
969
- });
970
- }
1057
+ await requestJson(socketInfo.socketPath, "POST", "/registry/upsert", DAEMON_TIMEOUT_MS.registry, {
1058
+ project: buildProjectEntry(initStatus),
1059
+ });
971
1060
  };
972
1061
  await runInitStep({
973
1062
  enabled: progressEnabled,
974
1063
  title: "Bootstrapping devbox",
975
- fn: async ({ fail }) => {
1064
+ fn: async ({ status, fail }) => {
976
1065
  try {
977
- await bootstrapDevbox(client, canonical);
1066
+ await retryInitStep({
1067
+ status,
1068
+ title: "Bootstrapping devbox",
1069
+ fn: async () => await bootstrapDevbox(client, canonical),
1070
+ });
978
1071
  }
979
1072
  catch (error) {
980
1073
  logger.warn("devbox_bootstrap_failed", {
@@ -982,12 +1075,7 @@ export const runInit = async (args) => {
982
1075
  error: String(error),
983
1076
  });
984
1077
  fail("Bootstrapping devbox (failed)");
985
- if (!parsed.json) {
986
- const message = error instanceof Error && error.message
987
- ? error.message
988
- : String(error);
989
- console.warn(`Warning: devbox bootstrap failed. ${message}`);
990
- }
1078
+ throw error;
991
1079
  }
992
1080
  },
993
1081
  });
@@ -1019,7 +1107,7 @@ export const runInit = async (args) => {
1019
1107
  await runInitStep({
1020
1108
  enabled: progressEnabled,
1021
1109
  title: "Installing sprite daemon",
1022
- fn: async ({ fail }) => {
1110
+ fn: async ({ status, fail }) => {
1023
1111
  try {
1024
1112
  const convexUrl = getConvexUrl();
1025
1113
  if (!controlPlaneToken) {
@@ -1036,12 +1124,16 @@ export const runInit = async (args) => {
1036
1124
  if (!heartbeatToken) {
1037
1125
  throw new Error("Daemon token unavailable.");
1038
1126
  }
1039
- await installSpriteDaemon({
1040
- client,
1041
- spriteName: canonical,
1042
- release,
1043
- convexUrl,
1044
- heartbeatToken,
1127
+ await retryInitStep({
1128
+ status,
1129
+ title: "Installing sprite daemon",
1130
+ fn: async () => await installSpriteDaemon({
1131
+ client,
1132
+ spriteName: canonical,
1133
+ release,
1134
+ convexUrl,
1135
+ heartbeatToken,
1136
+ }),
1045
1137
  });
1046
1138
  await updateInitState({ steps: { daemonInstalled: true } });
1047
1139
  }
@@ -1051,12 +1143,7 @@ export const runInit = async (args) => {
1051
1143
  error: String(error),
1052
1144
  });
1053
1145
  fail("Installing sprite daemon (failed)");
1054
- if (!parsed.json) {
1055
- const message = error instanceof Error && error.message
1056
- ? error.message
1057
- : String(error);
1058
- console.warn(`Warning: failed to install sprite daemon. ${message}`);
1059
- }
1146
+ throw error;
1060
1147
  }
1061
1148
  },
1062
1149
  });
@@ -1064,9 +1151,13 @@ export const runInit = async (args) => {
1064
1151
  await runInitStep({
1065
1152
  enabled: progressEnabled,
1066
1153
  title: "Ensuring sprite daemon service",
1067
- fn: async ({ fail }) => {
1154
+ fn: async ({ status, fail }) => {
1068
1155
  try {
1069
- await ensureSpriteDaemonService({ client, spriteName: canonical });
1156
+ await retryInitStep({
1157
+ status,
1158
+ title: "Ensuring sprite daemon service",
1159
+ fn: async () => await ensureSpriteDaemonService({ client, spriteName: canonical }),
1160
+ });
1070
1161
  await updateInitState({ steps: { daemonServiceEnsured: true } });
1071
1162
  }
1072
1163
  catch (error) {
@@ -1075,12 +1166,7 @@ export const runInit = async (args) => {
1075
1166
  error: String(error),
1076
1167
  });
1077
1168
  fail("Ensuring sprite daemon service (failed)");
1078
- if (!parsed.json) {
1079
- const message = error instanceof Error && error.message
1080
- ? error.message
1081
- : String(error);
1082
- console.warn(`Warning: failed to ensure sprite daemon service. ${message}`);
1083
- }
1169
+ throw error;
1084
1170
  }
1085
1171
  },
1086
1172
  });
@@ -1897,12 +1983,15 @@ export const runInit = async (args) => {
1897
1983
  enabled: progressEnabled,
1898
1984
  title: "Ensuring workdir ownership",
1899
1985
  fn: async ({ status }) => {
1900
- await ensureWorkdirOwnership({
1901
- client,
1902
- canonical,
1903
- workdir: expandedWorkdir,
1986
+ await retryInitStep({
1904
1987
  status,
1905
- json: parsed.json === true,
1988
+ title: "Ensuring workdir ownership",
1989
+ fn: async () => await ensureWorkdirOwnership({
1990
+ client,
1991
+ canonical,
1992
+ workdir: expandedWorkdir,
1993
+ status,
1994
+ }),
1906
1995
  });
1907
1996
  },
1908
1997
  });
@@ -1927,13 +2016,19 @@ export const runInit = async (args) => {
1927
2016
  await runInitStep({
1928
2017
  enabled: progressEnabled,
1929
2018
  title: "Configuring SSH mount access",
1930
- fn: async ({ fail }) => {
2019
+ fn: async ({ status, fail }) => {
1931
2020
  try {
1932
- await ensureLocalMountKey();
1933
- await ensureKnownHostsFile();
1934
- const publicKey = await readLocalMountPublicKey();
1935
- await ensureRemoteMountAccess(client, canonical, publicKey);
1936
- await ensureSshdService(client, canonical);
2021
+ await retryInitStep({
2022
+ status,
2023
+ title: "Configuring SSH mount access",
2024
+ fn: async () => {
2025
+ await ensureLocalMountKey();
2026
+ await ensureKnownHostsFile();
2027
+ const publicKey = await readLocalMountPublicKey();
2028
+ await ensureRemoteMountAccess(client, canonical, publicKey);
2029
+ await ensureSshdService(client, canonical);
2030
+ },
2031
+ });
1937
2032
  await updateInitState({ steps: { sshdConfigured: true } });
1938
2033
  }
1939
2034
  catch (error) {
@@ -1942,13 +2037,7 @@ export const runInit = async (args) => {
1942
2037
  error: String(error),
1943
2038
  });
1944
2039
  fail("Configuring SSH mount access (failed)");
1945
- if (!parsed.json) {
1946
- const message = error instanceof Error && error.message
1947
- ? error.message
1948
- : String(error);
1949
- console.warn(`Warning: failed to configure SSH mount access. ${message}`);
1950
- console.warn(`To retry later: dvb mount ${alias}`);
1951
- }
2040
+ throw error;
1952
2041
  }
1953
2042
  },
1954
2043
  });
@@ -2086,10 +2175,12 @@ export const runInit = async (args) => {
2086
2175
  }
2087
2176
  const weztermMuxPresent = await runInitStep({
2088
2177
  enabled: progressEnabled,
2089
- title: "Ensuring WezTerm mux server (optional)",
2090
- fn: async ({ fail }) => {
2091
- try {
2092
- const installResult = !shouldResume || !initState?.steps.weztermMuxInstalled
2178
+ title: "Ensuring WezTerm mux server",
2179
+ fn: async ({ status }) => {
2180
+ const installResult = await retryInitStep({
2181
+ status,
2182
+ title: "Ensuring WezTerm mux server",
2183
+ fn: async () => !shouldResume || !initState?.steps.weztermMuxInstalled
2093
2184
  ? await ensureWeztermMuxInstalled({
2094
2185
  client,
2095
2186
  spriteName: canonical,
@@ -2100,52 +2191,29 @@ export const runInit = async (args) => {
2100
2191
  spriteName: canonical,
2101
2192
  // If the mux is missing on resume, we still need to resolve a GitHub asset.
2102
2193
  allowResolveAsset: true,
2103
- });
2104
- if (installResult.muxPresent) {
2105
- await updateInitState({ steps: { weztermMuxInstalled: true } });
2106
- }
2107
- return installResult.muxPresent;
2108
- }
2109
- catch (error) {
2110
- logger.warn("wezterm_mux_install_failed", {
2111
- box: canonical,
2112
- error: error instanceof Error ? error.message : String(error),
2113
- });
2114
- fail("Ensuring WezTerm mux server (optional) (failed)");
2115
- if (!parsed.json) {
2116
- const message = error instanceof Error && error.message
2117
- ? error.message
2118
- : String(error);
2119
- console.warn(`Warning: failed to install WezTerm mux server (optional). ${message}\n` +
2120
- "Tip: re-run with DEVBOX_LOG_LEVEL=info to see Sprite exec logs on stderr.");
2121
- }
2122
- return false;
2194
+ }),
2195
+ });
2196
+ if (installResult.muxPresent) {
2197
+ await updateInitState({ steps: { weztermMuxInstalled: true } });
2198
+ return true;
2123
2199
  }
2200
+ throw new Error("WezTerm mux server unavailable.");
2124
2201
  },
2125
2202
  });
2126
- if (weztermMuxPresent &&
2127
- (!shouldResume || !initState?.steps.weztermMuxServiceEnsured)) {
2203
+ if (!shouldResume || !initState?.steps.weztermMuxServiceEnsured) {
2128
2204
  await runInitStep({
2129
2205
  enabled: progressEnabled,
2130
- title: "Ensuring WezTerm mux service (optional)",
2131
- fn: async ({ fail }) => {
2132
- try {
2133
- await ensureWeztermMuxService({ client, spriteName: canonical });
2134
- await updateInitState({ steps: { weztermMuxServiceEnsured: true } });
2135
- }
2136
- catch (error) {
2137
- logger.warn("wezterm_mux_service_failed", {
2138
- box: canonical,
2139
- error: error instanceof Error ? error.message : String(error),
2140
- });
2141
- fail("Ensuring WezTerm mux service (optional) (failed)");
2142
- if (!parsed.json) {
2143
- const message = error instanceof Error && error.message
2144
- ? error.message
2145
- : String(error);
2146
- console.warn(`Warning: failed to ensure WezTerm mux service (optional). ${message}`);
2147
- }
2206
+ title: "Ensuring WezTerm mux service",
2207
+ fn: async ({ status }) => {
2208
+ if (!weztermMuxPresent) {
2209
+ throw new Error("WezTerm mux server unavailable.");
2148
2210
  }
2211
+ await retryInitStep({
2212
+ status,
2213
+ title: "Ensuring WezTerm mux service",
2214
+ fn: async () => await ensureWeztermMuxService({ client, spriteName: canonical }),
2215
+ });
2216
+ await updateInitState({ steps: { weztermMuxServiceEnsured: true } });
2149
2217
  },
2150
2218
  });
2151
2219
  }
@@ -2213,20 +2281,13 @@ export const runInit = async (args) => {
2213
2281
  });
2214
2282
  await runInitStep({
2215
2283
  enabled: progressEnabled,
2216
- title: "Updating Codex config on sprite (optional)",
2217
- fn: async ({ fail }) => {
2218
- try {
2219
- await writeRemoteCodexConfig(client, canonical, repoName);
2220
- }
2221
- catch (error) {
2222
- logger.warn("codex_config_update_failed", {
2223
- error: String(error),
2224
- });
2225
- fail("Updating Codex config on sprite (optional) (failed)");
2226
- if (!parsed.json) {
2227
- console.warn("Warning: failed to update /home/sprite/.codex/config.toml for full-access sandbox settings.");
2228
- }
2229
- }
2284
+ title: "Updating Codex config on sprite",
2285
+ fn: async ({ status }) => {
2286
+ await retryInitStep({
2287
+ status,
2288
+ title: "Updating Codex config on sprite",
2289
+ fn: async () => await writeRemoteCodexConfig(client, canonical, repoName),
2290
+ });
2230
2291
  },
2231
2292
  });
2232
2293
  const skipSetupArtifactsStage = skipCodexApply ||
@@ -2251,13 +2312,17 @@ export const runInit = async (args) => {
2251
2312
  if (!skipCodexApply) {
2252
2313
  await runInitStep({
2253
2314
  enabled: progressEnabled,
2254
- title: "Snapshotting filesystem (pre-setup) (optional)",
2255
- fn: async ({ fail, ok }) => {
2315
+ title: "Snapshotting filesystem (pre-setup)",
2316
+ fn: async ({ status, fail, ok }) => {
2256
2317
  try {
2257
- const checkpoint = await recordCodexCheckpoint({
2258
- client,
2259
- canonical,
2260
- phase: "preCodexSetup",
2318
+ const checkpoint = await retryInitStep({
2319
+ status,
2320
+ title: "Snapshotting filesystem (pre-setup)",
2321
+ fn: async () => await recordCodexCheckpoint({
2322
+ client,
2323
+ canonical,
2324
+ phase: "preCodexSetup",
2325
+ }),
2261
2326
  });
2262
2327
  if (checkpoint.id) {
2263
2328
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -2270,10 +2335,8 @@ export const runInit = async (args) => {
2270
2335
  phase: "pre-codex-setup",
2271
2336
  error: error instanceof Error ? error.message : String(error),
2272
2337
  });
2273
- fail("Snapshotting filesystem (pre-setup) (optional) (failed)");
2274
- if (!parsed.json) {
2275
- console.warn("Warning: failed to create pre-setup filesystem snapshot. Continuing without it.");
2276
- }
2338
+ fail("Snapshotting filesystem (pre-setup) (failed)");
2339
+ throw error;
2277
2340
  }
2278
2341
  },
2279
2342
  });
@@ -2314,13 +2377,17 @@ export const runInit = async (args) => {
2314
2377
  await updateRegistryProjectStatus(resolveInitStatus(initState?.steps, initState?.complete));
2315
2378
  await runInitStep({
2316
2379
  enabled: progressEnabled,
2317
- title: "Snapshotting filesystem (post-setup) (optional)",
2318
- fn: async ({ fail, ok }) => {
2380
+ title: "Snapshotting filesystem (post-setup)",
2381
+ fn: async ({ status, fail, ok }) => {
2319
2382
  try {
2320
- const checkpoint = await recordCodexCheckpoint({
2321
- client,
2322
- canonical,
2323
- phase: "postCodexSetup",
2383
+ const checkpoint = await retryInitStep({
2384
+ status,
2385
+ title: "Snapshotting filesystem (post-setup)",
2386
+ fn: async () => await recordCodexCheckpoint({
2387
+ client,
2388
+ canonical,
2389
+ phase: "postCodexSetup",
2390
+ }),
2324
2391
  });
2325
2392
  if (checkpoint.id) {
2326
2393
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -2333,10 +2400,8 @@ export const runInit = async (args) => {
2333
2400
  phase: "post-codex-setup",
2334
2401
  error: error instanceof Error ? error.message : String(error),
2335
2402
  });
2336
- fail("Snapshotting filesystem (post-setup) (optional) (failed)");
2337
- if (!parsed.json) {
2338
- console.warn("Warning: failed to create post-setup filesystem snapshot. Continuing without it.");
2339
- }
2403
+ fail("Snapshotting filesystem (post-setup) (failed)");
2404
+ throw error;
2340
2405
  }
2341
2406
  },
2342
2407
  });