diff --git a/apps/backend/api/config-github-apply.ts b/apps/backend/api/config-github-apply.ts
new file mode 100644
index 000000000..59deed829
--- /dev/null
+++ b/apps/backend/api/config-github-apply.ts
@@ -0,0 +1,8 @@
+import app from "../dist/vercel.mjs";
+
+export const config = {
+ runtime: "nodejs",
+ maxDuration: 800,
+};
+
+export default app;
diff --git a/apps/backend/api/config-github-commit.ts b/apps/backend/api/config-github-commit.ts
new file mode 100644
index 000000000..74b64ab32
--- /dev/null
+++ b/apps/backend/api/config-github-commit.ts
@@ -0,0 +1,8 @@
+import app from "../dist/vercel.mjs";
+
+export const config = {
+ runtime: "nodejs",
+ maxDuration: 120,
+};
+
+export default app;
diff --git a/apps/backend/src/server/app.ts b/apps/backend/src/server/app.ts
index 047278972..2bc0c5dba 100644
--- a/apps/backend/src/server/app.ts
+++ b/apps/backend/src/server/app.ts
@@ -178,6 +178,33 @@ import.meta.vitest?.test("API version migrations do not expose their internal re
}
});
+import.meta.vitest?.test("uncaught dispatch errors use the global sanitized error boundary", async ({ expect }) => {
+ const { vi } = import.meta.vitest!;
+ vi.stubEnv("NODE_ENV", "production");
+ vi.stubEnv("HEXCLAVE_ARTIFICIAL_DEVELOPMENT_DELAY_MS", "1");
+ vi.stubEnv("STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS", "1");
+
+ try {
+ const response = await app.handle(new Request("http://localhost/api/v1"));
+
+ expect({
+ status: response.status,
+ body: await response.text(),
+ contentType: response.headers.get("content-type"),
+ contentTypeOptions: response.headers.get("x-content-type-options"),
+ }).toMatchInlineSnapshot(`
+ {
+ "body": "Internal Server Error",
+ "contentType": "text/plain; charset=utf-8",
+ "contentTypeOptions": "nosniff",
+ "status": 500,
+ }
+ `);
+ } finally {
+ vi.unstubAllEnvs();
+ }
+});
+
function homeHtml() {
const devStatsLink = getNodeEnvironment() === "development"
? `
Dev Stats
`
diff --git a/apps/backend/src/server/vercel-config.test.ts b/apps/backend/src/server/vercel-config.test.ts
new file mode 100644
index 000000000..b83eb08b5
--- /dev/null
+++ b/apps/backend/src/server/vercel-config.test.ts
@@ -0,0 +1,31 @@
+import { readFile } from "node:fs/promises";
+import { describe, expect, it } from "vitest";
+
+describe("Vercel function durations", () => {
+ it("preserves the route-specific limits from the Next.js deployment", async () => {
+ const [vercelJson, applyEntry, commitEntry] = await Promise.all([
+ readFile(new URL("../../vercel.json", import.meta.url), "utf8"),
+ readFile(new URL("../../api/config-github-apply.ts", import.meta.url), "utf8"),
+ readFile(new URL("../../api/config-github-commit.ts", import.meta.url), "utf8"),
+ ]);
+
+ expect(JSON.parse(vercelJson)).toMatchObject({
+ rewrites: [
+ {
+ source: "/api/:version/internal/config/github/apply",
+ destination: "/api/config-github-apply",
+ },
+ {
+ source: "/api/:version/internal/config/github/commit",
+ destination: "/api/config-github-commit",
+ },
+ {
+ source: "/(.*)",
+ destination: "/api",
+ },
+ ],
+ });
+ expect(applyEntry).toContain("maxDuration: 800");
+ expect(commitEntry).toContain("maxDuration: 120");
+ });
+});
diff --git a/apps/backend/vercel.json b/apps/backend/vercel.json
index 921bc8f0d..fb7b45f13 100644
--- a/apps/backend/vercel.json
+++ b/apps/backend/vercel.json
@@ -2,6 +2,8 @@
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": null,
"rewrites": [
+ { "source": "/api/:version/internal/config/github/apply", "destination": "/api/config-github-apply" },
+ { "source": "/api/:version/internal/config/github/commit", "destination": "/api/config-github-commit" },
{ "source": "/(.*)", "destination": "/api" }
],
"crons": [