@vellumai/cli 0.8.10-dev.202606110422.8c0e9aa → 0.8.10-dev.202606110544.2aed335

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.8.10-dev.202606110422.8c0e9aa",
3
+ "version": "0.8.10-dev.202606110544.2aed335",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,6 +16,7 @@ import {
16
16
  platformRequestSignedUrl,
17
17
  readPlatformToken,
18
18
  } from "../lib/platform-client.js";
19
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
19
20
 
20
21
  export async function backup(): Promise<void> {
21
22
  const args = process.argv.slice(3);
@@ -112,7 +113,7 @@ export async function backup(): Promise<void> {
112
113
  // Call the export endpoint
113
114
  let response: Response;
114
115
  try {
115
- response = await fetch(`${entry.runtimeUrl}/v1/migrations/export`, {
116
+ response = await loopbackSafeFetch(`${entry.runtimeUrl}/v1/migrations/export`, {
116
117
  method: "POST",
117
118
  headers: {
118
119
  Authorization: `Bearer ${accessToken}`,
@@ -138,7 +139,7 @@ export async function backup(): Promise<void> {
138
139
  }
139
140
  if (refreshedToken) {
140
141
  accessToken = refreshedToken;
141
- response = await fetch(`${entry.runtimeUrl}/v1/migrations/export`, {
142
+ response = await loopbackSafeFetch(`${entry.runtimeUrl}/v1/migrations/export`, {
142
143
  method: "POST",
143
144
  headers: {
144
145
  Authorization: `Bearer ${accessToken}`,
@@ -56,6 +56,7 @@ import {
56
56
  readPlatformToken,
57
57
  } from "../lib/platform-client";
58
58
  import { tuiLog } from "../lib/tui-log";
59
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
59
60
 
60
61
  const SUPPORTED_INTERFACES = ["cli", "web"] as const;
61
62
  type SupportedInterface = (typeof SUPPORTED_INTERFACES)[number];
@@ -619,7 +620,7 @@ async function handleLocalEndpoints(
619
620
 
620
621
  try {
621
622
  const hasBody = req.method !== "GET" && req.method !== "HEAD";
622
- const proxyRes = await fetch(targetUrl, {
623
+ const proxyRes = await loopbackSafeFetch(targetUrl, {
623
624
  method: req.method,
624
625
  headers,
625
626
  body: hasBody ? req.body : undefined,
@@ -760,7 +761,7 @@ async function runWebInterface(
760
761
  try {
761
762
  const hasBody = req.method !== "GET" && req.method !== "HEAD";
762
763
  const body = hasBody ? await req.arrayBuffer() : undefined;
763
- const proxyRes = await fetch(target.toString(), {
764
+ const proxyRes = await loopbackSafeFetch(target.toString(), {
764
765
  method: req.method,
765
766
  headers,
766
767
  body,
@@ -28,6 +28,7 @@ import {
28
28
  canPromptForConfirmation,
29
29
  confirmAction,
30
30
  } from "../lib/confirm-action.js";
31
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
31
32
 
32
33
  interface DeviceRecord {
33
34
  hashedDeviceId: string;
@@ -108,7 +109,7 @@ async function listDevices(entry: AssistantEntry, base: string): Promise<void> {
108
109
 
109
110
  let response: Response;
110
111
  try {
111
- response = await fetch(`${base}/v1/devices`, {
112
+ response = await loopbackSafeFetch(`${base}/v1/devices`, {
112
113
  method: "GET",
113
114
  headers: getClientRegistrationHeaders(CLI_INTERFACE_ID),
114
115
  });
@@ -186,7 +187,7 @@ async function revokeDevice(
186
187
 
187
188
  let response: Response;
188
189
  try {
189
- response = await fetch(`${base}/v1/devices/revoke`, {
190
+ response = await loopbackSafeFetch(`${base}/v1/devices/revoke`, {
190
191
  method: "POST",
191
192
  headers: {
192
193
  "Content-Type": "application/json",
@@ -26,6 +26,7 @@ import {
26
26
  } from "../lib/client-identity.js";
27
27
  import { GATEWAY_PORT } from "../lib/constants.js";
28
28
  import { getLocalLanIPv4 } from "../lib/local.js";
29
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
29
30
 
30
31
  function isLoopbackHost(url: string): boolean {
31
32
  try {
@@ -154,7 +155,7 @@ export async function pair(): Promise<void> {
154
155
 
155
156
  let response: Response;
156
157
  try {
157
- response = await fetch(`${mintUrl}/v1/pair`, {
158
+ response = await loopbackSafeFetch(`${mintUrl}/v1/pair`, {
158
159
  method: "POST",
159
160
  headers: {
160
161
  "Content-Type": "application/json",
@@ -16,6 +16,7 @@ import {
16
16
  platformPollJobStatus,
17
17
  } from "../lib/platform-client.js";
18
18
  import { performDockerRollback } from "../lib/upgrade-lifecycle.js";
19
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
19
20
 
20
21
  function printUsage(): void {
21
22
  console.log(
@@ -588,7 +589,7 @@ export async function restore(): Promise<void> {
588
589
 
589
590
  let response: Response;
590
591
  try {
591
- response = await fetch(
592
+ response = await loopbackSafeFetch(
592
593
  `${entry.runtimeUrl}/v1/migrations/import-preflight`,
593
594
  {
594
595
  method: "POST",
@@ -694,7 +695,7 @@ export async function restore(): Promise<void> {
694
695
 
695
696
  let response: Response;
696
697
  try {
697
- response = await fetch(`${entry.runtimeUrl}/v1/migrations/import`, {
698
+ response = await loopbackSafeFetch(`${entry.runtimeUrl}/v1/migrations/import`, {
698
699
  method: "POST",
699
700
  headers: {
700
701
  Authorization: `Bearer ${accessToken}`,
@@ -36,6 +36,7 @@ import {
36
36
  resetLogFile,
37
37
  writeToLogFile,
38
38
  } from "../lib/xdg-log.js";
39
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
39
40
 
40
41
  export { retireLocal };
41
42
 
@@ -96,7 +97,7 @@ async function retireVellum(
96
97
 
97
98
  const platformUrl = runtimeUrl || getPlatformUrl();
98
99
  const url = `${platformUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
99
- const response = await fetch(url, {
100
+ const response = await loopbackSafeFetch(url, {
100
101
  method: "DELETE",
101
102
  headers: await authHeaders(token, runtimeUrl),
102
103
  });
@@ -1,4 +1,5 @@
1
1
  import { readPlatformToken, getWebUrl } from "../lib/platform-client.js";
2
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
2
3
 
3
4
  function printUsage(): void {
4
5
  console.log("Usage: vellum roadmap <subcommand>");
@@ -88,7 +89,7 @@ async function apiFetch(
88
89
  if (options.token) headers["X-Session-Token"] = options.token;
89
90
  if (options.body) headers["Content-Type"] = "application/json";
90
91
 
91
- return fetch(url, {
92
+ return loopbackSafeFetch(url, {
92
93
  method: options.method ?? "GET",
93
94
  headers,
94
95
  body: options.body ? JSON.stringify(options.body) : undefined,
@@ -47,6 +47,7 @@ import {
47
47
  waitForReady,
48
48
  } from "../lib/upgrade-lifecycle.js";
49
49
  import { compareVersions } from "../lib/version-compat.js";
50
+ import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
50
51
 
51
52
  interface UpgradeArgs {
52
53
  name: string | null;
@@ -230,7 +231,7 @@ async function upgradeDocker(
230
231
  lastWorkspaceMigrationId?: string;
231
232
  } = {};
232
233
  try {
233
- const healthResp = await fetch(
234
+ const healthResp = await loopbackSafeFetch(
234
235
  `${entry.runtimeUrl}/healthz?include=migrations`,
235
236
  {
236
237
  signal: AbortSignal.timeout(5000),
@@ -695,7 +696,7 @@ async function upgradePlatform(
695
696
  body.version = version;
696
697
  }
697
698
 
698
- const response = await fetch(url, {
699
+ const response = await loopbackSafeFetch(url, {
699
700
  method: "POST",
700
701
  headers,
701
702
  body: JSON.stringify(body),
@@ -19,6 +19,7 @@ import {
19
19
  refreshGuardianToken,
20
20
  guardianTokenDueForRenewal,
21
21
  } from "./guardian-token.js";
22
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
22
23
 
23
24
  const DEFAULT_TIMEOUT_MS = 30_000;
24
25
  const FALLBACK_RUNTIME_URL = `http://127.0.0.1:${GATEWAY_PORT}`;
@@ -203,7 +204,7 @@ export class AssistantClient {
203
204
  const doFetch = (): Promise<Response> => {
204
205
  const headers = buildHeaders();
205
206
  if (opts?.signal) {
206
- return fetch(url, {
207
+ return loopbackSafeFetch(url, {
207
208
  method,
208
209
  headers,
209
210
  body: jsonBody,
@@ -213,7 +214,7 @@ export class AssistantClient {
213
214
  const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;
214
215
  const controller = new AbortController();
215
216
  const timeoutId = setTimeout(() => controller.abort(), timeout);
216
- return fetch(url, {
217
+ return loopbackSafeFetch(url, {
217
218
  method,
218
219
  headers,
219
220
  body: jsonBody,
@@ -13,6 +13,7 @@ import {
13
13
  loadGuardianToken,
14
14
  refreshGuardianToken,
15
15
  } from "./guardian-token.js";
16
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
16
17
 
17
18
  /** Default backup directory following XDG convention */
18
19
  export function getBackupsDir(): string {
@@ -66,7 +67,7 @@ export async function createBackup(
66
67
  return null;
67
68
  }
68
69
 
69
- let response = await fetch(`${runtimeUrl}/v1/migrations/export`, {
70
+ let response = await loopbackSafeFetch(`${runtimeUrl}/v1/migrations/export`, {
70
71
  method: "POST",
71
72
  headers: {
72
73
  Authorization: `Bearer ${accessToken}`,
@@ -87,7 +88,7 @@ export async function createBackup(
87
88
  return null;
88
89
  }
89
90
  accessToken = refreshed.accessToken;
90
- response = await fetch(`${runtimeUrl}/v1/migrations/export`, {
91
+ response = await loopbackSafeFetch(`${runtimeUrl}/v1/migrations/export`, {
91
92
  method: "POST",
92
93
  headers: {
93
94
  Authorization: `Bearer ${accessToken}`,
@@ -152,7 +153,7 @@ export async function restoreBackup(
152
153
  return false;
153
154
  }
154
155
 
155
- let response = await fetch(`${runtimeUrl}/v1/migrations/import`, {
156
+ let response = await loopbackSafeFetch(`${runtimeUrl}/v1/migrations/import`, {
156
157
  method: "POST",
157
158
  headers: {
158
159
  Authorization: `Bearer ${accessToken}`,
@@ -171,7 +172,7 @@ export async function restoreBackup(
171
172
  return false;
172
173
  }
173
174
  accessToken = refreshed.accessToken;
174
- response = await fetch(`${runtimeUrl}/v1/migrations/import`, {
175
+ response = await loopbackSafeFetch(`${runtimeUrl}/v1/migrations/import`, {
175
176
  method: "POST",
176
177
  headers: {
177
178
  Authorization: `Bearer ${accessToken}`,
package/src/lib/docker.ts CHANGED
@@ -67,6 +67,7 @@ export {
67
67
  ASSISTANT_INTERNAL_PORT,
68
68
  GATEWAY_INTERNAL_PORT,
69
69
  } from "./environments/paths.js";
70
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
70
71
 
71
72
  /** Max time to wait for the assistant container to emit the readiness sentinel. */
72
73
  export const DOCKER_READY_TIMEOUT_MS = 5 * 60 * 1000;
@@ -1530,7 +1531,7 @@ async function waitForGatewayAndLease(opts: {
1530
1531
 
1531
1532
  while (Date.now() - start < DOCKER_READY_TIMEOUT_MS) {
1532
1533
  try {
1533
- const resp = await fetch(readyUrl, {
1534
+ const resp = await loopbackSafeFetch(readyUrl, {
1534
1535
  signal: AbortSignal.timeout(5000),
1535
1536
  });
1536
1537
  if (resp.ok) {
@@ -24,6 +24,7 @@ import {
24
24
 
25
25
  import { getConfigDir } from "./environments/paths.js";
26
26
  import { getCurrentEnvironment } from "./environments/resolve.js";
27
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
27
28
 
28
29
  const DEVICE_ID_SALT = "vellum-assistant-host-id";
29
30
 
@@ -346,7 +347,7 @@ export async function refreshGuardianToken(
346
347
 
347
348
  const tokenData = current ?? before;
348
349
 
349
- const response = await fetch(`${gatewayUrl}/v1/guardian/refresh`, {
350
+ const response = await loopbackSafeFetch(`${gatewayUrl}/v1/guardian/refresh`, {
350
351
  method: "POST",
351
352
  headers: {
352
353
  "Content-Type": "application/json",
@@ -406,7 +407,7 @@ export async function leaseGuardianToken(
406
407
  if (bootstrapSecret) {
407
408
  headers["x-bootstrap-secret"] = bootstrapSecret;
408
409
  }
409
- const response = await fetch(`${gatewayUrl}/v1/guardian/init`, {
410
+ const response = await loopbackSafeFetch(`${gatewayUrl}/v1/guardian/init`, {
410
411
  method: "POST",
411
412
  headers,
412
413
  body: JSON.stringify({ platform: "cli", deviceId }),
@@ -44,6 +44,7 @@ import {
44
44
  } from "./provider-secrets.js";
45
45
  import { logHatchNextSteps } from "./hatch-next-steps.js";
46
46
  import { checkProviderApiKey } from "./api-key-check.js";
47
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
47
48
 
48
49
  /**
49
50
  * Attempts to place a symlink at the given path pointing to cliBinary.
@@ -358,7 +359,7 @@ export async function hatchLocal(
358
359
  while (true) {
359
360
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
360
361
  try {
361
- const res = await fetch(healthUrl, {
362
+ const res = await loopbackSafeFetch(healthUrl, {
362
363
  signal: AbortSignal.timeout(3000),
363
364
  });
364
365
  if (res.ok) {
@@ -1,3 +1,5 @@
1
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
2
+
1
3
  /**
2
4
  * Build the base URL for the daemon HTTP server.
3
5
  */
@@ -15,7 +17,7 @@ export async function httpHealthCheck(
15
17
  ): Promise<boolean> {
16
18
  try {
17
19
  const url = `${buildDaemonUrl(port)}/healthz`;
18
- const response = await fetch(url, {
20
+ const response = await loopbackSafeFetch(url, {
19
21
  signal: AbortSignal.timeout(timeoutMs),
20
22
  });
21
23
  return response.ok;
@@ -9,6 +9,7 @@ import {
9
9
  resolveRuntimeMigrationUrl,
10
10
  resolveRuntimeUrl,
11
11
  } from "./runtime-url.js";
12
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
12
13
 
13
14
  /**
14
15
  * Thrown when the local runtime returns 409 for an export/import request
@@ -122,7 +123,7 @@ export async function localRuntimeExportToGcs(
122
123
  body.description = params.description;
123
124
  }
124
125
 
125
- const response = await fetch(
126
+ const response = await loopbackSafeFetch(
126
127
  resolveRuntimeMigrationUrl(entry, "export-to-gcs"),
127
128
  {
128
129
  method: "POST",
@@ -166,7 +167,7 @@ export async function localRuntimeImportFromGcs(
166
167
  token: string,
167
168
  params: { bundleUrl: string },
168
169
  ): Promise<{ jobId: string }> {
169
- const response = await fetch(
170
+ const response = await loopbackSafeFetch(
170
171
  resolveRuntimeMigrationUrl(entry, "import-from-gcs"),
171
172
  {
172
173
  method: "POST",
@@ -211,7 +212,7 @@ export async function localRuntimePollJobStatus(
211
212
  token: string,
212
213
  jobId: string,
213
214
  ): Promise<UnifiedJobStatus> {
214
- const response = await fetch(
215
+ const response = await loopbackSafeFetch(
215
216
  resolveRuntimeMigrationUrl(entry, `jobs/${jobId}`),
216
217
  {
217
218
  headers: await migrationRequestHeaders(entry, token),
@@ -285,7 +286,7 @@ export async function localRuntimeIdentity(
285
286
  ): Promise<RuntimeIdentity> {
286
287
  const url = resolveRuntimeUrl(entry, "health");
287
288
  const doRequest = async (): Promise<Response> =>
288
- fetch(url, {
289
+ loopbackSafeFetch(url, {
289
290
  method: "GET",
290
291
  headers: await migrationRequestHeaders(entry, token),
291
292
  });
package/src/lib/ngrok.ts CHANGED
@@ -11,6 +11,7 @@ import { homedir } from "node:os";
11
11
  import { dirname, join } from "node:path";
12
12
 
13
13
  import { GATEWAY_PORT } from "./constants";
14
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
14
15
 
15
16
  function getDefaultWorkspaceDir(): string {
16
17
  return (
@@ -78,7 +79,7 @@ export function getNgrokVersion(): string | null {
78
79
  */
79
80
  async function queryNgrokTunnels(): Promise<NgrokTunnel[] | null> {
80
81
  try {
81
- const res = await fetch(NGROK_API_URL, {
82
+ const res = await loopbackSafeFetch(NGROK_API_URL, {
82
83
  signal: AbortSignal.timeout(2_000),
83
84
  });
84
85
  if (!res.ok) return null;
@@ -1,6 +1,7 @@
1
1
  import { getPlatformUrl } from "./platform-client.js";
2
2
  import { DOCKERHUB_IMAGES } from "./docker.js";
3
3
  import type { ServiceName } from "./docker.js";
4
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
4
5
 
5
6
  export interface ResolvedImageRefs {
6
7
  imageTags: Record<ServiceName, string>;
@@ -15,7 +16,7 @@ export interface ResolvedImageRefs {
15
16
  export async function fetchLatestStableVersion(): Promise<string | null> {
16
17
  try {
17
18
  const platformUrl = getPlatformUrl();
18
- const response = await fetch(`${platformUrl}/v1/releases/?stable=true`, {
19
+ const response = await loopbackSafeFetch(`${platformUrl}/v1/releases/?stable=true`, {
19
20
  signal: AbortSignal.timeout(10_000),
20
21
  });
21
22
  if (!response.ok) return null;
@@ -80,7 +81,7 @@ async function fetchPlatformImageRefs(
80
81
 
81
82
  log?.(`Fetching releases from ${url}`);
82
83
 
83
- const response = await fetch(url, {
84
+ const response = await loopbackSafeFetch(url, {
84
85
  signal: AbortSignal.timeout(10_000),
85
86
  });
86
87
 
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { authHeaders, getPlatformUrl } from "./platform-client.js";
10
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
10
11
 
11
12
  // ---------------------------------------------------------------------------
12
13
  // Create / Close
@@ -25,7 +26,7 @@ export async function createTerminalSession(
25
26
  if (service) {
26
27
  body.service = service;
27
28
  }
28
- const response = await fetch(
29
+ const response = await loopbackSafeFetch(
29
30
  `${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/`,
30
31
  {
31
32
  method: "POST",
@@ -49,7 +50,7 @@ export async function closeTerminalSession(
49
50
  platformUrl?: string,
50
51
  ): Promise<void> {
51
52
  const baseUrl = platformUrl || getPlatformUrl();
52
- const response = await fetch(
53
+ const response = await loopbackSafeFetch(
53
54
  `${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/`,
54
55
  {
55
56
  method: "DELETE",
@@ -76,7 +77,7 @@ export async function sendTerminalInput(
76
77
  platformUrl?: string,
77
78
  ): Promise<void> {
78
79
  const baseUrl = platformUrl || getPlatformUrl();
79
- const response = await fetch(
80
+ const response = await loopbackSafeFetch(
80
81
  `${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/input/`,
81
82
  {
82
83
  method: "POST",
@@ -100,7 +101,7 @@ export async function resizeTerminalSession(
100
101
  platformUrl?: string,
101
102
  ): Promise<void> {
102
103
  const baseUrl = platformUrl || getPlatformUrl();
103
- const response = await fetch(
104
+ const response = await loopbackSafeFetch(
104
105
  `${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/resize/`,
105
106
  {
106
107
  method: "POST",
@@ -137,7 +138,7 @@ export async function* subscribeTerminalEvents(
137
138
  signal?: AbortSignal,
138
139
  ): AsyncGenerator<TerminalOutputEvent> {
139
140
  const baseUrl = platformUrl || getPlatformUrl();
140
- const response = await fetch(
141
+ const response = await loopbackSafeFetch(
141
142
  `${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/events/`,
142
143
  {
143
144
  headers: await authHeaders(token, platformUrl),
@@ -27,6 +27,7 @@ import {
27
27
  } from "./statefulset.js";
28
28
  import { exec, execOutput } from "./step-runner.js";
29
29
  import { compareVersions } from "./version-compat.js";
30
+ import { loopbackSafeFetch } from "./loopback-fetch.js";
30
31
 
31
32
  // ---------------------------------------------------------------------------
32
33
  // Failure log capture
@@ -274,7 +275,7 @@ export async function fetchCurrentVersion(
274
275
  runtimeUrl: string,
275
276
  ): Promise<string | undefined> {
276
277
  try {
277
- const resp = await fetch(`${runtimeUrl}/healthz`, {
278
+ const resp = await loopbackSafeFetch(`${runtimeUrl}/healthz`, {
278
279
  signal: AbortSignal.timeout(5000),
279
280
  });
280
281
  if (resp.ok) {
@@ -299,7 +300,7 @@ export async function fetchAssistantIngressUrl(
299
300
  ): Promise<string | undefined> {
300
301
  if (!bearerToken) return undefined;
301
302
  try {
302
- const resp = await fetch(`${runtimeUrl}/integrations/ingress/config`, {
303
+ const resp = await loopbackSafeFetch(`${runtimeUrl}/integrations/ingress/config`, {
303
304
  headers: { Authorization: `Bearer ${bearerToken}` },
304
305
  signal: AbortSignal.timeout(5000),
305
306
  });
@@ -341,7 +342,7 @@ export async function fetchPreviousVersion(
341
342
  try {
342
343
  const { getPlatformUrl } = await import("./platform-client.js");
343
344
  const platformUrl = getPlatformUrl();
344
- const resp = await fetch(`${platformUrl}/v1/releases/?stable=true`, {
345
+ const resp = await loopbackSafeFetch(`${platformUrl}/v1/releases/?stable=true`, {
345
346
  signal: AbortSignal.timeout(10_000),
346
347
  });
347
348
  if (!resp.ok) return undefined;
@@ -373,7 +374,7 @@ export async function waitForReady(runtimeUrl: string): Promise<boolean> {
373
374
 
374
375
  while (Date.now() - start < DOCKER_READY_TIMEOUT_MS) {
375
376
  try {
376
- const resp = await fetch(readyUrl, {
377
+ const resp = await loopbackSafeFetch(readyUrl, {
377
378
  signal: AbortSignal.timeout(5000),
378
379
  });
379
380
  if (resp.ok) {
@@ -419,7 +420,7 @@ export async function broadcastUpgradeEvent(
419
420
  if (token?.accessToken) {
420
421
  headers["Authorization"] = `Bearer ${token.accessToken}`;
421
422
  }
422
- await fetch(`${gatewayUrl}/v1/admin/upgrade-broadcast`, {
423
+ await loopbackSafeFetch(`${gatewayUrl}/v1/admin/upgrade-broadcast`, {
423
424
  method: "POST",
424
425
  headers,
425
426
  body: JSON.stringify(event),
@@ -448,7 +449,7 @@ export async function commitWorkspaceViaGateway(
448
449
  if (token?.accessToken) {
449
450
  headers["Authorization"] = `Bearer ${token.accessToken}`;
450
451
  }
451
- await fetch(`${gatewayUrl}/v1/admin/workspace-commit`, {
452
+ await loopbackSafeFetch(`${gatewayUrl}/v1/admin/workspace-commit`, {
452
453
  method: "POST",
453
454
  headers,
454
455
  body: JSON.stringify({ message }),
@@ -491,7 +492,7 @@ export async function rollbackMigrations(
491
492
  body.targetWorkspaceMigrationId = targetWorkspaceMigrationId;
492
493
  if (rollbackToRegistryCeiling) body.rollbackToRegistryCeiling = true;
493
494
 
494
- const resp = await fetch(`${gatewayUrl}/v1/admin/rollback-migrations`, {
495
+ const resp = await loopbackSafeFetch(`${gatewayUrl}/v1/admin/rollback-migrations`, {
495
496
  method: "POST",
496
497
  headers,
497
498
  body: JSON.stringify(body),
@@ -572,7 +573,7 @@ export async function performDockerRollback(
572
573
  lastWorkspaceMigrationId?: string;
573
574
  } = {};
574
575
  try {
575
- const healthResp = await fetch(
576
+ const healthResp = await loopbackSafeFetch(
576
577
  `${entry.runtimeUrl}/healthz?include=migrations`,
577
578
  { signal: AbortSignal.timeout(5000) },
578
579
  );