Codegen Troubleshooting
Diagnoses the three regeneration failures that break generated SDK clients: schema drift in MemoryJsonValue, stale imports missing.js extensions, and mismatched Content-Type headers on optional-body requests.
Both the Node client in api-core and the Python client under primitive.api run a post-generation fixup script that repairs known generator output problems. When a generator version bump changes that output, the fixup either throws or stops matching, and this page lists the failures you'll see.
For the end-to-end regeneration command sequence, see Regenerating SDK Code from the OpenAPI Spec. For how the pipeline fits together, see Architecture: Shared Codegen Pipeline.
Unable to repair generated MemoryJsonValue type#
fixMemoryJsonValueType in packages/api-core/scripts/fix-generated-api-imports.ts throws this when its regex no longer matches the generated MemoryJsonValue alias:
Unable to repair generated MemoryJsonValue type in <file>. The codegen output shape changed; update fixMemoryJsonValueType before publishing.
Cause: @hey-api/openapi-ts treats the OpenAPI 3.1 type: "null" branch of this recursive JSON schema as unknown, which widens the whole generated alias, so the fixup script patches the alias back to the exact JSON type (see Generated TypeScript Client Fixups for the full explanation of this repair). It matches the generated block with a regex replacement:
// packages/api-core/scripts/fix-generated-api-imports.ts
const MEMORY_JSON_VALUE_PATTERN =
/export type MemoryJsonValue = [\s\S]*?;\n\n\/\*\*\n \* Memory scope\./;
That pattern anchors on the literal text immediately following the generated type, including the * Memory scope. docstring on the next exported symbol. If the OpenAPI spec's schema ordering changes, or @hey-api/openapi-ts changes its comment or type-emission format, the pattern no longer matches and the script throws rather than silently leaving the unknown-widened type in place.
Fix: open the newly generated file (under packages/api-core/src/api/) and check what export type MemoryJsonValue = ... and the symbol that follows it actually look like now. Update MEMORY_JSON_VALUE_PATTERN and MEMORY_JSON_VALUE_TYPE in fix-generated-api-imports.ts to match the new shape, then re-run:
cd packages/api-core
pnpm generate:api
Confirm the fix by grepping the output for the exact type:
# from packages/api-core
grep -rA3 "export type MemoryJsonValue" src/api
You should see the null-inclusive union string | number | boolean | Array<MemoryJsonValue> | { [key: string]: MemoryJsonValue } | null, not unknown. See Memory Value Validation Helper for how isMemoryJsonValue depends on this type staying accurate.
Generated imports missing .js extensions#
Relative imports in the generated TypeScript need a .js extension for Node ESM resolution, and @hey-api/openapi-ts emits them without one, so unresolvable-module errors mean the fixup pass did not run.
Cause: the generator emits relative specifiers such as from "./types", and fix-generated-api-imports.ts rewrites them to add .js (or /index.js for directory imports), as described in Generated TypeScript Client Fixups:
// packages/api-core/scripts/fix-generated-api-imports.ts
function addJsExtension(file: string, specifier: string): string {
if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
return specifier;
}
if (specifier.endsWith(".js") || specifier.endsWith(".json")) {
return specifier;
}
// ...resolves whether the target is a directory (adds /index.js)
// or a file (adds .js)
}
Fix: this step runs automatically as part of pnpm generate:api (which calls openapi-ts then tsx scripts/fix-generated-api-imports.ts). If you see unfixed imports, you likely ran the raw openapi-ts generator directly and skipped the fixup script. Re-run the full sequence:
cd packages/api-core
pnpm generate:api
Never hand-edit import paths in generated files, the next regeneration overwrites them.
Optional-body request sends Content-Type: application/json with no body#
The generator adds the JSON Content-Type header to every operation that declares a request body, so optional-body operations send the header even when the caller omits the payload, which is wrong on the wire.
Cause: @hey-api/openapi-ts unconditionally emits:
// generated operation, before the fixup
headers: { 'Content-Type': 'application/json', ...options.headers }
on every operation whose OpenAPI schema declares a request body, regardless of whether that body is required. The optional-body operations affected are testFunction, cli_logout, start_cli_login, and search_emails. fix-generated-api-imports.ts guards this with guardOptionalBodyContentType, which rewrites the header assignment to fire only when a body is actually present (see Generated TypeScript Client Fixups):
// packages/api-core/scripts/fix-generated-api-imports.ts
function guardOptionalBodyContentType(content: string): string {
return content.replace(
/(headers:\s*\{\n\s*)'Content-Type':\s*'application\/json',\n(\s*\.\.\.options\.headers\n\s*\})/g,
"$1...(options.body !== undefined && { 'Content-Type': 'application/json' }),\n$2",
);
}
Required-body operations are unaffected: the type system guarantees body is always defined for those, so the header still ships in practice.
Fix: this transform runs automatically inside fix-generated-api-imports.ts. If a generated operation still emits the unconditional header:
- Confirm you ran
pnpm generate:api(not the rawopenapi-tscommand alone). - Check whether the operation's shape in the generated source still matches the regex,
@hey-api/openapi-tsversion bumps can reformat the emittedheaders: {...}block (different whitespace, reordered keys) and silently break the match. Diff the generated file'sheaders:block against the pattern and update the regex inguardOptionalBodyContentTypeif the format changed. - Re-run generation and grep for the guarded form:
# from packages/api-core
grep -rA2 "'Content-Type'" src/api
You should see ...(options.body !== undefined && { 'Content-Type': 'application/json' }), not a bare 'Content-Type': 'application/json',.
The Python client has the analogous problem and fix in sdk-python/scripts/generate_api_client.py's guard_optional_body_content_type, which re-indents the same unconditional header assignment so it only fires inside the if not isinstance(body, Unset): block. If you hit the Python equivalent, re-run:
cd sdk-python
uv run python scripts/generate_api_client.py
and confirm with:
# from sdk-python
grep -rB2 'headers\["Content-Type"\] = "application/json"' src/primitive/api/api
The line should be indented inside the if not isinstance(body, Unset): block, not at the function's top level.
Duplicate imports in the generated Python client#
openapi-python-client 0.28.3 occasionally emits the same import line twice, so strict linters such as ruff and basedpyright flag repeated from ... import ... lines in generated model files.
Cause: the duplication shows up most often as from ..types import UNSET, Unset in 201-response models. It is semantically a no-op but trips downstream strict linters.
Fix: generate_api_client.py's dedupe_imports runs automatically after generation and strips repeated module-level import lines (it only matches unindented lines, so imports inside TYPE_CHECKING blocks or function bodies are left alone). If you still see duplicates after generating, confirm you ran the wrapper script rather than calling openapi-python-client generate directly:
cd sdk-python
uv run python scripts/generate_api_client.py
File payload responses wrap response.text instead of response.content#
Generated Python file-download responses wrap the response in BytesIO, which requires bytes, so openapi-python-client's BytesIO(response.text) output has to be rewritten.
Fix: use_bytes_for_file_responses in sdk-python/scripts/generate_api_client.py rewrites BytesIO(response.text) to BytesIO(response.content) automatically, as part of the same generate_api_client.py invocation above. There is no separate command to run.
Not sure which fixup is out of sync#
Regenerate everything from the OpenAPI source of truth rather than patching generated files by hand:
make node-generate python-generate go-generate
Never hand-edit files under packages/api-core/src/api/ or sdk-python/src/primitive/api/. Any manual fix is silently overwritten the next time someone runs the generator. Fix the script that produces the file, not the file itself.
After regenerating, run each SDK's checks to catch anything the fixups missed:
make node-check python-check go-check
Was this page helpful?