Skip to content

Commit 03a8ea8

Browse files
Braden-suiclaude
andcommitted
fix(vibe-publish): preserve HTTP status/body when wrapping errors
Error wrapping was losing HTTP status (like 401) making isAuthError detection fail. The fix ensures status and body are copied to the outer error in createCapsule, uploadFile, publishCapsule, and publishVibe. This allows withAuthRetry to properly detect 401 errors and trigger token refresh even when errors are wrapped. Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 652a5ca commit 03a8ea8

File tree

2 files changed

+61
-4
lines changed

2 files changed

+61
-4
lines changed

lib/vibe-publish.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,17 @@ export const createCapsule = async ({
205205
throw err;
206206
}
207207

208-
throw createError({
208+
// Preserve status and body on the outer error for auth detection
209+
const wrappedErr = createError({
209210
...CapsuleCreateError,
210211
message: `Failed to create capsule: ${err.message}`,
211212
cause: err,
212213
url,
213214
title,
214215
});
216+
if (err.status !== undefined) wrappedErr.status = err.status;
217+
if (err.body !== undefined) wrappedErr.body = err.body;
218+
throw wrappedErr;
215219
}
216220
};
217221

@@ -384,14 +388,18 @@ export const uploadFile = async ({
384388
});
385389
}
386390

387-
throw createError({
391+
// Preserve status and body on the outer error for auth detection
392+
const wrappedErr = createError({
388393
...FileUploadError,
389394
message: `Failed to upload file '${filePath}': ${err.message}`,
390395
cause: err,
391396
url,
392397
path: filePath,
393398
capsuleId,
394399
});
400+
if (err.status !== undefined) wrappedErr.status = err.status;
401+
if (err.body !== undefined) wrappedErr.body = err.body;
402+
throw wrappedErr;
395403
}
396404
};
397405

@@ -484,13 +492,17 @@ export const publishCapsule = async ({
484492
throw err;
485493
}
486494

487-
throw createError({
495+
// Preserve status and body on the outer error for auth detection
496+
const wrappedErr = createError({
488497
...CapsulePublishError,
489498
message: `Failed to publish capsule: ${err.message}`,
490499
cause: err,
491500
url,
492501
capsuleId,
493502
});
503+
if (err.status !== undefined) wrappedErr.status = err.status;
504+
if (err.body !== undefined) wrappedErr.body = err.body;
505+
throw wrappedErr;
494506
}
495507
};
496508

@@ -734,13 +746,19 @@ export const publishVibe = async ({
734746
const errCode = err.cause?.code || err.code;
735747
const recoveryData = extractRecoveryData(err, files.length);
736748

737-
throw createError({
749+
// Preserve status and body on the outer error for auth detection
750+
// isAuthError checks err.status === 401, so we must copy it up
751+
const wrappedErr = createError({
738752
...PublishFailed,
739753
message: `Publish failed: ${err.message}`,
740754
cause: err,
741755
title,
742756
...recoveryData,
743757
});
758+
// Copy HTTP-related properties for error handlers (e.g., isAuthError, withAuthRetry)
759+
if (err.status !== undefined) wrappedErr.status = err.status;
760+
if (err.body !== undefined) wrappedErr.body = err.body;
761+
throw wrappedErr;
744762
}
745763
};
746764

lib/vibe-publish.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,4 +1223,43 @@ describe("publishVibe - partial failure scenarios", () => {
12231223
expected: "CAPSULE_CREATE_ERROR",
12241224
});
12251225
});
1226+
1227+
test("preserves HTTP 401 status for isAuthError detection", async () => {
1228+
// Mock create capsule - fails with 401
1229+
mockFetch.mockResolvedValueOnce({
1230+
ok: false,
1231+
status: 401,
1232+
text: async () => JSON.stringify({ error: "Unauthorized" }),
1233+
});
1234+
1235+
let error;
1236+
try {
1237+
await publishVibe({
1238+
apiBase: "http://localhost:8787",
1239+
token: "expired-token",
1240+
title: "Test Vibe",
1241+
files: [{ path: "App.tsx", content: "content" }],
1242+
});
1243+
} catch (e) {
1244+
error = e;
1245+
}
1246+
1247+
// The wrapped error should still be detectable as an auth error
1248+
// isAuthError checks err.status === 401
1249+
assert({
1250+
given: "API returns 401 unauthorized",
1251+
should: "have status preserved for isAuthError detection",
1252+
actual: isAuthError(error),
1253+
expected: true,
1254+
});
1255+
1256+
// Also verify the error has PUBLISH_FAILED code
1257+
assert({
1258+
given: "API returns 401 unauthorized",
1259+
should: "throw PUBLISH_FAILED error",
1260+
actual: error?.cause?.code,
1261+
expected: "PUBLISH_FAILED",
1262+
});
1263+
});
1264+
12261265
});

0 commit comments

Comments
 (0)