“You’re calling OpenAI from Node.js. 99% of requests work. Then randomly:
BadRequestError: 400 could not parse JSON body
Or:
SyntaxError: Invalid JSON: EOF while parsing an object
Or, if you’re using tool calling:
Invalid JSON in tool call arguments
You check your code. Nothing changed. You retry manually — it works. You deploy again. It breaks again.
What’s actually happening
These errors are NOT your bugs. They’re transient artifacts from:
- Proxy / network corruption — the request body arrives at OpenAI partially mangled. OpenAI returns 400.
- Streaming truncation — the SDK parses JSON before the stream signals completion. Especially with max_output_tokens set low.
- Model-side tool call malformation — the model emits single quotes, trailing commas, or markdown code fences inside function.arguments.
Every one of these is intermittent. So you can’t debug it with a stack trace. You just suffer.
The wrong fix
People usually do this:
try {
const res = await client.chat.completions.create({...});
} catch (e) {
const res = await client.chat.completions.create({...});
}
Enter fullscreen mode Exit fullscreen mode
Problems:
- You retry errors that should NOT be retried (invalid API key, rate limit that needs backoff, context length exceeded).
- You don’t sanitize tool call args, so malformed JSON still crashes downstream.
- You add 20 lines of retry boilerplate per call site.
The right fix: classify then act
Different errors need different handling:
Error Correct action Transient 400 (could not parse JSON body) Retry with backoff + jitter Truncated stream (EOF while parsing) Retry, possibly with higher max_tokens Malformed tool args Sanitize inline, no retry needed Invalid API key Fail fast, do NOT retry Rate limit Retry with longer backoffDoing this manually is ~150 lines of code you’ll write badly. Or:
npm install @coder12-z/llm-shield
import { shield } from "@coder12-z/llm-shield";
import OpenAI from "openai";
const client = new OpenAI();
const safeCall = shield(async (prompt) =>
client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
response_format: { type: "json_object" },
}),
{
maxRetries: 3,
onRetry: ({ attempt, kind }) => console.log(`retry #${attempt} (${kind})`),
}
);
Enter fullscreen mode Exit fullscreen mode
Three lines of config. Zero dependencies. Works with OpenAI, Anthropic, Gemini, or anything that throws Error objects with status and message.
What it doesn’t do
- It doesn’t repair arbitrary JSON strings — for that, use jsonrepair (https://www.npmjs.com/package/jsonrepair).
- It doesn’t call LLMs for you — it wraps your existing calls.
- No API key. No network calls. No telemetry.
Links
- npm: https://www.npmjs.com/package/@coder12-z/llm-shield
- GitHub: https://github.com/zey-netizen/llm-shield
- Landing: https://zey-netizen.github.io/llm-shield/
MIT licensed. Feedback welcome.