From 4f5f3ec658acfcfb262d699095ac2d72b62bb90e Mon Sep 17 00:00:00 2001 From: "will.kendall" Date: Fri, 14 Aug 2026 20:18:17 +0000 Subject: [PATCH 1/6] fix: stop sending dynamic IR bodies in registerApiDefinition request Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ...ister-api-definition-payload-too-large.yml | 6 +++++ .../toRegisterDynamicIRsInput.test.ts | 25 +++++++++++++++++++ .../src/publishDocs.ts | 3 ++- .../src/toRegisterDynamicIRsInput.ts | 18 +++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml create mode 100644 packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts create mode 100644 packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts diff --git a/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml b/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml new file mode 100644 index 000000000000..72c73b9ba0d7 --- /dev/null +++ b/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml @@ -0,0 +1,6 @@ +- summary: | + Fix `PAYLOAD_TOO_LARGE` failures when publishing docs for large APIs with dynamic snippets. The + dynamic IRs were being sent inline in the `registerApiDefinition` request once per SDK language, + even though the registry only needs the language names to issue upload URLs (the IRs are uploaded + to S3 separately). + type: fix diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts new file mode 100644 index 000000000000..9de5ad0d6ae2 --- /dev/null +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; +import { toRegisterDynamicIRsInput } from "../toRegisterDynamicIRsInput.js"; + +describe("toRegisterDynamicIRsInput", () => { + it("returns undefined when there are no dynamic IRs", () => { + expect(toRegisterDynamicIRsInput(undefined)).toBeUndefined(); + }); + + it("preserves the language keys", () => { + const result = toRegisterDynamicIRsInput({ + python: { dynamicIR: { types: {} } }, + typescript: { dynamicIR: { types: {} } } + }); + + expect(Object.keys(result ?? {}).sort()).toEqual(["python", "typescript"]); + }); + + it("strips the IR bodies so they are not sent in the registration request", () => { + const dynamicIR = { types: { User: { name: "User" } } }; + const result = toRegisterDynamicIRsInput({ python: { dynamicIR }, go: { dynamicIR } }); + + expect(result).toEqual({ python: {}, go: {} }); + expect(JSON.stringify(result)).not.toContain("User"); + }); +}); diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts index 9be6aec06e2a..df50f9477b98 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts @@ -69,6 +69,7 @@ import { normalizeRepoUrlToHttps } from "./normalizeRepoUrl.js"; import { publishDocsViaLedger } from "./publishDocsLedger.js"; import { publishDocsViaLedgerPreview } from "./publishDocsLedgerPreview.js"; import { retryWithBackoff } from "./retryWithBackoff.js"; +import { toRegisterDynamicIRsInput } from "./toRegisterDynamicIRsInput.js"; import { asyncPool } from "./utils/asyncPool.js"; const MEASURE_IMAGE_BATCH_SIZE = 10; @@ -487,7 +488,7 @@ export async function publishDocs({ orgId: CjsFdrSdk.OrgId(organization), apiId: CjsFdrSdk.ApiId(effectiveApiName), definition: apiDefinition, - dynamicIRs: dynamicIRsByLanguage + dynamicIRs: toRegisterDynamicIRsInput(dynamicIRsByLanguage) }), maxRetries: REGISTER_MAX_RETRIES, baseDelayMs: REGISTER_BASE_DELAY_MS, diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts new file mode 100644 index 000000000000..8e2028f42e94 --- /dev/null +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts @@ -0,0 +1,18 @@ +import type { APIV1Write } from "@fern-api/fdr-sdk"; + +type DynamicIr = APIV1Write.DynamicIr; + +/** + * FDR only reads the language keys of `dynamicIRs` when registering an API definition — it mints one + * presigned upload URL per language, and the IRs themselves are uploaded directly to S3 afterwards. + * Sending the IR bodies inline duplicates the entire IR once per language in the registration request + * body, which can exceed the server's request size limit for large APIs. + */ +export function toRegisterDynamicIRsInput( + dynamicIRsByLanguage: Record | undefined +): Record | undefined { + if (dynamicIRsByLanguage == null) { + return undefined; + } + return Object.fromEntries(Object.keys(dynamicIRsByLanguage).map((language) => [language, {}])); +} From 8edc65aab8a932d2c0cdd44e364f097d68e19917 Mon Sep 17 00:00:00 2001 From: "will.kendall" Date: Fri, 14 Aug 2026 20:46:21 +0000 Subject: [PATCH 2/6] chore(cli): log registerApiDefinition request body size Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/publishDocs.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts index df50f9477b98..e350eaf52d8a 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts @@ -480,16 +480,22 @@ export async function publishDocs({ const effectiveApiName = apiName ?? getOriginalName(ir.apiName); + const registerApiDefinitionRequest = { + orgId: CjsFdrSdk.OrgId(organization), + apiId: CjsFdrSdk.ApiId(effectiveApiName), + definition: apiDefinition, + dynamicIRs: toRegisterDynamicIRsInput(dynamicIRsByLanguage) + }; + context.logger.debug( + `registerApiDefinition request body for ${effectiveApiName}: ${Buffer.byteLength( + JSON.stringify(registerApiDefinitionRequest) + )} bytes, ${Object.keys(dynamicIRsByLanguage ?? {}).length} dynamic IR language(s) (IR bodies are uploaded separately)` + ); + let response; try { response = await retryWithBackoff({ - fn: () => - fdr.api.register.registerApiDefinition({ - orgId: CjsFdrSdk.OrgId(organization), - apiId: CjsFdrSdk.ApiId(effectiveApiName), - definition: apiDefinition, - dynamicIRs: toRegisterDynamicIRsInput(dynamicIRsByLanguage) - }), + fn: () => fdr.api.register.registerApiDefinition(registerApiDefinitionRequest), maxRetries: REGISTER_MAX_RETRIES, baseDelayMs: REGISTER_BASE_DELAY_MS, jitterFactor: REGISTER_JITTER_FACTOR, From 0d69c793004bc3db3503ffe59072b3290b64ee6d Mon Sep 17 00:00:00 2001 From: "will.kendall" Date: Fri, 14 Aug 2026 20:56:59 +0000 Subject: [PATCH 3/6] chore: retrigger ci Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> From 0d1c495f5dfae54cc8ab314d20fd438fb9050904 Mon Sep 17 00:00:00 2001 From: "will.kendall" Date: Tue, 18 Aug 2026 16:25:06 +0000 Subject: [PATCH 4/6] fix(cli): send dynamicIRLanguages on registerApiDefinition Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../cli/changes/unreleased/send-dynamic-ir-languages.yml | 6 ++++++ .../remote-workspace-runner/src/publishDocs.ts | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml diff --git a/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml b/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml new file mode 100644 index 000000000000..68a1711365c1 --- /dev/null +++ b/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml @@ -0,0 +1,6 @@ +- summary: | + Send `dynamicIRLanguages` on `registerApiDefinition` instead of a map of empty dynamic IR objects. + The registry only needs the SDK language names to issue upload URLs; the IRs themselves are still + uploaded to S3 separately. The legacy `dynamicIRs` field is still sent for registries that predate + `dynamicIRLanguages`. + type: fix diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts index e350eaf52d8a..4d4fa1d73398 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts @@ -480,16 +480,19 @@ export async function publishDocs({ const effectiveApiName = apiName ?? getOriginalName(ir.apiName); + const dynamicIRLanguages = dynamicIRsByLanguage != null ? Object.keys(dynamicIRsByLanguage) : undefined; const registerApiDefinitionRequest = { orgId: CjsFdrSdk.OrgId(organization), apiId: CjsFdrSdk.ApiId(effectiveApiName), definition: apiDefinition, + dynamicIRLanguages, + // for registries that predate `dynamicIRLanguages`; the IR bodies are stripped either way dynamicIRs: toRegisterDynamicIRsInput(dynamicIRsByLanguage) }; context.logger.debug( `registerApiDefinition request body for ${effectiveApiName}: ${Buffer.byteLength( JSON.stringify(registerApiDefinitionRequest) - )} bytes, ${Object.keys(dynamicIRsByLanguage ?? {}).length} dynamic IR language(s) (IR bodies are uploaded separately)` + )} bytes, ${dynamicIRLanguages?.length ?? 0} dynamic IR language(s) (IR bodies are uploaded separately)` ); let response; From 81982ad68a04cf60e69ecb225026faa67ca747ae Mon Sep 17 00:00:00 2001 From: "will.kendall" Date: Tue, 18 Aug 2026 19:02:11 +0000 Subject: [PATCH 5/6] fix(cli): send only dynamicIRLanguages on registerApiDefinition Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../unreleased/send-dynamic-ir-languages.yml | 7 +++--- .../toRegisterDynamicIRsInput.test.ts | 25 ------------------- .../src/publishDocs.ts | 5 +--- .../src/toRegisterDynamicIRsInput.ts | 18 ------------- 4 files changed, 4 insertions(+), 51 deletions(-) delete mode 100644 packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts delete mode 100644 packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts diff --git a/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml b/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml index 68a1711365c1..ef7fbeac40d8 100644 --- a/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml +++ b/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml @@ -1,6 +1,5 @@ - summary: | - Send `dynamicIRLanguages` on `registerApiDefinition` instead of a map of empty dynamic IR objects. - The registry only needs the SDK language names to issue upload URLs; the IRs themselves are still - uploaded to S3 separately. The legacy `dynamicIRs` field is still sent for registries that predate - `dynamicIRLanguages`. + Send `dynamicIRLanguages` on `registerApiDefinition` instead of a map of dynamic IR objects. The + registry only needs the SDK language names to issue upload URLs; the IRs themselves are still + uploaded to S3 separately. type: fix diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts deleted file mode 100644 index 9de5ad0d6ae2..000000000000 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/__test__/toRegisterDynamicIRsInput.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { toRegisterDynamicIRsInput } from "../toRegisterDynamicIRsInput.js"; - -describe("toRegisterDynamicIRsInput", () => { - it("returns undefined when there are no dynamic IRs", () => { - expect(toRegisterDynamicIRsInput(undefined)).toBeUndefined(); - }); - - it("preserves the language keys", () => { - const result = toRegisterDynamicIRsInput({ - python: { dynamicIR: { types: {} } }, - typescript: { dynamicIR: { types: {} } } - }); - - expect(Object.keys(result ?? {}).sort()).toEqual(["python", "typescript"]); - }); - - it("strips the IR bodies so they are not sent in the registration request", () => { - const dynamicIR = { types: { User: { name: "User" } } }; - const result = toRegisterDynamicIRsInput({ python: { dynamicIR }, go: { dynamicIR } }); - - expect(result).toEqual({ python: {}, go: {} }); - expect(JSON.stringify(result)).not.toContain("User"); - }); -}); diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts index 4d4fa1d73398..1753cc57ff97 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/publishDocs.ts @@ -69,7 +69,6 @@ import { normalizeRepoUrlToHttps } from "./normalizeRepoUrl.js"; import { publishDocsViaLedger } from "./publishDocsLedger.js"; import { publishDocsViaLedgerPreview } from "./publishDocsLedgerPreview.js"; import { retryWithBackoff } from "./retryWithBackoff.js"; -import { toRegisterDynamicIRsInput } from "./toRegisterDynamicIRsInput.js"; import { asyncPool } from "./utils/asyncPool.js"; const MEASURE_IMAGE_BATCH_SIZE = 10; @@ -485,9 +484,7 @@ export async function publishDocs({ orgId: CjsFdrSdk.OrgId(organization), apiId: CjsFdrSdk.ApiId(effectiveApiName), definition: apiDefinition, - dynamicIRLanguages, - // for registries that predate `dynamicIRLanguages`; the IR bodies are stripped either way - dynamicIRs: toRegisterDynamicIRsInput(dynamicIRsByLanguage) + dynamicIRLanguages }; context.logger.debug( `registerApiDefinition request body for ${effectiveApiName}: ${Buffer.byteLength( diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts deleted file mode 100644 index 8e2028f42e94..000000000000 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/toRegisterDynamicIRsInput.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { APIV1Write } from "@fern-api/fdr-sdk"; - -type DynamicIr = APIV1Write.DynamicIr; - -/** - * FDR only reads the language keys of `dynamicIRs` when registering an API definition — it mints one - * presigned upload URL per language, and the IRs themselves are uploaded directly to S3 afterwards. - * Sending the IR bodies inline duplicates the entire IR once per language in the registration request - * body, which can exceed the server's request size limit for large APIs. - */ -export function toRegisterDynamicIRsInput( - dynamicIRsByLanguage: Record | undefined -): Record | undefined { - if (dynamicIRsByLanguage == null) { - return undefined; - } - return Object.fromEntries(Object.keys(dynamicIRsByLanguage).map((language) => [language, {}])); -} From c994b28e58b1e6342f0c39a50353bf7ca5afba2f Mon Sep 17 00:00:00 2001 From: "will.kendall" Date: Tue, 18 Aug 2026 19:07:45 +0000 Subject: [PATCH 6/6] chore(cli): consolidate changelog entry Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../fix-register-api-definition-payload-too-large.yml | 6 ------ .../cli/changes/unreleased/send-dynamic-ir-languages.yml | 7 ++++--- 2 files changed, 4 insertions(+), 9 deletions(-) delete mode 100644 packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml diff --git a/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml b/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml deleted file mode 100644 index 72c73b9ba0d7..000000000000 --- a/packages/cli/cli/changes/unreleased/fix-register-api-definition-payload-too-large.yml +++ /dev/null @@ -1,6 +0,0 @@ -- summary: | - Fix `PAYLOAD_TOO_LARGE` failures when publishing docs for large APIs with dynamic snippets. The - dynamic IRs were being sent inline in the `registerApiDefinition` request once per SDK language, - even though the registry only needs the language names to issue upload URLs (the IRs are uploaded - to S3 separately). - type: fix diff --git a/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml b/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml index ef7fbeac40d8..ee8475b99067 100644 --- a/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml +++ b/packages/cli/cli/changes/unreleased/send-dynamic-ir-languages.yml @@ -1,5 +1,6 @@ - summary: | - Send `dynamicIRLanguages` on `registerApiDefinition` instead of a map of dynamic IR objects. The - registry only needs the SDK language names to issue upload URLs; the IRs themselves are still - uploaded to S3 separately. + Fix `PAYLOAD_TOO_LARGE` failures when publishing docs for large APIs with dynamic snippets. The + dynamic IRs were sent inline in the `registerApiDefinition` request once per SDK language, even + though the registry only needs the language names to issue upload URLs. The request now carries + `dynamicIRLanguages` and the IRs themselves are still uploaded to S3 separately. type: fix