Skip to content

Commit 11cda9e

Browse files
committed
fix/transcript-404-empty-reason
1 parent 8e97e44 commit 11cda9e

4 files changed

Lines changed: 54 additions & 3 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Return a not-found response when a missing transcript receives an object-store 404 without a reason phrase

‎apps/webapp/app/services/realtime/transcriptDownload.server.test.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createServer, type Server } from "node:http";
22
import { once } from "node:events";
3+
import { createServer as createTcpServer } from "node:net";
34
import express from "express";
45
import { sendRemixResponse } from "@remix-run/express/dist/server";
56
import { expect, test } from "vitest";
@@ -116,6 +117,34 @@ test("does not treat permission or service failures as an absent transcript", ()
116117
expect(isTranscriptNotFound({ name: "NoSuchKey" })).toBe(true);
117118
});
118119

120+
test("recognizes a missing transcript when the object store omits the reason phrase", async () => {
121+
const server = createTcpServer((socket) => {
122+
socket.once("data", () => {
123+
socket.end("HTTP/1.1 404 \r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
124+
});
125+
});
126+
server.listen(0, "127.0.0.1");
127+
await once(server, "listening");
128+
const address = server.address();
129+
if (!address || typeof address === "string") throw new Error("Expected TCP listener");
130+
131+
try {
132+
const client = ObjectStoreClient.create({
133+
baseUrl: `http://127.0.0.1:${address.port}`,
134+
accessKeyId: "test-access-key",
135+
secretAccessKey: "test-secret-key",
136+
service: "s3",
137+
});
138+
139+
// @crumbs Exercise the adapter with a valid 404 response whose statusText is empty.
140+
await expect(client.getObjectResponse(key)).rejects.toSatisfy(isTranscriptNotFound);
141+
} finally {
142+
await new Promise<void>((resolve, reject) =>
143+
server.close((error) => (error ? reject(error) : resolve()))
144+
);
145+
}
146+
});
147+
119148
minioTest(
120149
"HEAD distinguishes permission failures from missing objects",
121150
async ({ minioConfig }) => {

‎apps/webapp/app/services/realtime/transcriptDownload.server.ts‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,20 @@ export function downloadTranscript(
4545

4646
export function isTranscriptNotFound(error: unknown): boolean {
4747
if (!error || typeof error !== "object") return false;
48-
const { name, $metadata } = error as { name?: string; $metadata?: { httpStatusCode?: number } };
49-
if (name === "NoSuchKey" || name === "NotFound" || $metadata?.httpStatusCode === 404) return true;
48+
const { name, status, $metadata } = error as {
49+
name?: string;
50+
status?: number;
51+
$metadata?: { httpStatusCode?: number };
52+
};
53+
// @crumbs Prefer status codes because HTTP reason phrases are optional.
54+
if (
55+
name === "NoSuchKey" ||
56+
name === "NotFound" ||
57+
status === 404 ||
58+
$metadata?.httpStatusCode === 404
59+
) {
60+
return true;
61+
}
5062
// The aws4fetch adapter currently reports the HTTP status text in its error.
5163
return (
5264
error instanceof Error &&

‎apps/webapp/app/v3/objectStoreClient.server.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,11 @@ class Aws4FetchClient implements IObjectStoreClient {
135135
async getObjectResponse(key: string): Promise<Response> {
136136
const response = await this.awsClient.fetch(this.buildUrl(key));
137137
if (!response.ok) {
138-
throw new Error(`Failed to download from object store: ${response.statusText}`);
138+
// @crumbs Preserve the numeric status because statusText may be empty for valid responses.
139+
throw Object.assign(
140+
new Error(`Failed to download from object store: ${response.statusText}`),
141+
{ status: response.status }
142+
);
139143
}
140144
return response;
141145
}

0 commit comments

Comments
 (0)