Originally published on kuryzhev.cloud
A content workflow automation built in n8n passes every test with one topic, then fails the first time an editor submits twenty. Some drafts never arrive, a few arrive twice, and one is cut off mid-sentence and sits in WordPress as a "ready" post. This runbook covers the failure modes that show up when n8n orchestrates Amazon Bedrock calls, and the changes that remove them.
Symptoms
Several symptoms often appear together, but each points to a different cause.
- ThrottlingException or HTTP 429 from Bedrock when a batch starts, often within the first few items depending on your quota and model.
- ValidationException mentioning the model identifier or saying on-demand throughput is not supported for that model. The exact wording is version- and model-dependent.
- AccessDeniedException on a model that works in the console, commonly because the IAM role, the region, or the account's model access differs.
- 504 or "Gateway Timeout" returned to the caller of an n8n webhook while the execution keeps running in the background.
- Duplicate posts after a retry, a restart, or a client that resent the webhook.
- Broken or truncated output: invalid JSON in a "parse" step, missing closing sections, or markdown fences inside the post body.
If only the first symptom is present, jump to Fix #1. If posts are duplicated, start with Fix #2.
Root cause
These failures usually come from treating an LLM call like an ordinary fast API call inside a content workflow automation. Three assumptions break under real load.
Assumption 1: capacity is unlimited. Bedrock enforces per-model, per-region quotas on requests and tokens per minute. n8n's default behavior is to push items through a node as fast as it can, so a batch of items becomes a burst. The documented behavior is a throttling error, and quotas differ by model and account. Verify yours in the Bedrock quotas documentation and the Service Quotas console.
Assumption 2: the call finishes before the caller gives up. Long generations can take tens of seconds, depending on the model and output length. A webhook that waits for the whole chain hits the proxy or load balancer timeout first, the client retries, and the workflow runs again.
Assumption 3: the output is trustworthy. A model can stop at the token limit, wrap JSON in prose, or return a different structure than requested. If the next node publishes whatever arrives, the defect lands in production.
Watch out for the interaction between 2 and 3: a retry after a timeout is non-idempotent by default, so each retry produces a fresh, slightly different article.
Fix #1: Control concurrency and retry throttling
Slow the burst down and let retries absorb the remainder. In n8n, put the Bedrock call inside a Loop Over Items (Split In Batches) node with a batch size of 1 to 3, and add a Wait node when quotas are tight. On the node that makes the Bedrock request (for example HTTP Request with AWS authentication), enable Retry On Fail with a wait between tries of several seconds. n8n caps the number of tries, so treat this as a complement to batching, not a replacement.
If the call runs in a Lambda worker instead of an n8n node, let the AWS SDK for JavaScript v3 do the backoff, and set the function timeout well above the expected generation time plus retries. The sketch below uses the Converse API, which gives one request shape across supported models.
import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";
const client = new BedrockRuntimeClient({
region: process.env.AWS_REGION,
maxAttempts: 8, // SDK retries throttling errors with backoff
retryMode: "adaptive", // adds client-side rate limiting on throttles
});
export const handler = async (event) => {
const res = await client.send(new ConverseCommand({
modelId: process.env.MODEL_ID, // inference profile ID/ARN if the model requires one
messages: [{ role: "user", content: [{ text: event.prompt }] }],
inferenceConfig: { maxTokens: 2000, temperature: 0.3 },
}));
return {
text: res.output.message.content[0].text,
stopReason: res.stopReason, // "max_tokens" means the draft was cut off
};
};
Two gotchas here. Some newer models are only invokable through a cross-region inference profile, so the plain model ID triggers the ValidationException above; check the model's page in the Bedrock documentation. Also, the IAM policy must allow bedrock:InvokeModel on the profile and on the underlying models in each region it routes to. If throttling persists at low volume, request a quota increase rather than adding more retries.
Fix #2: Acknowledge fast and make runs idempotent
Stop the webhook from waiting on the generation. Set the Webhook node's response mode to respond immediately, return a job ID, and let the rest of the workflow run asynchronously. For sustained volume, run n8n in queue mode so workers pull executions from Redis and a concurrency cap protects Bedrock.
# docker-compose.yml excerpt (no top-level "version" key needed)
services:
n8n-worker:
image: docker.n8n.io/n8nio/n8n:latest # pin an exact tag in real deployments
command: worker --concurrency=3 # parallel executions per worker
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- EXECUTIONS_TIMEOUT=600 # kill runaway executions (seconds)
- NODE_FUNCTION_ALLOW_BUILTIN=crypto # lets Code nodes hash input for dedupe
Variable names and worker flags change between n8n releases, and workers also need the same database and encryption key settings as the main instance. Confirm everything in the n8n environment variable reference.
Then add an idempotency key. Hash the topic, target keyword, and requester into a stable ID, and claim it atomically before generating anything, for example by inserting it into a Postgres table with a unique constraint. If the insert fails because the key exists, exit the run. A separate lookup followed by a write lets two concurrent duplicates both pass. Do not use the execution ID as the key: a resent webhook or a restart starts a new execution with a new ID, which defeats the check.
Fix #3: Validate output before anything publishes
Add a gate between generation and publishing. Check the stop reason, parse the structure, and enforce basic rules. Anything that fails goes to a review queue, not to the CMS.
This n8n Code node sketch assumes the previous step returns text and stopReason.
// n8n Code node, "Run Once for Each Item"
const { text, stopReason } = $json;
const errors = [];
// plain-text generation should end with "end_turn"; tool-use flows return other reasons
if (stopReason !== "end_turn") errors.push(`unexpected stop: ${stopReason}`);
let post;
try {
// strip accidental markdown fences before parsing
post = JSON.parse(text.replace(/^
```(?:json)?\s*|\s*```
$/g, ""));
} catch (e) {
errors.push("invalid JSON");
}
if (post) {
if (!post.title || post.title.length > 70) errors.push("bad title");
if (!post.body || post.body.split(/\s+/).length < 600) errors.push("body too short");
if (post.body && /lorem ipsum|as an ai/i.test(post.body)) errors.push("placeholder text");
}
return { json: { ok: errors.length === 0, errors, post } };
Route on ok with an IF node. Passing items should be created as drafts via the WordPress node, never straight to published. Where the model supports it, tool use or structured output in the Converse request can reduce parse failures further, though support varies by model. Verify with the Bedrock documentation.
Prevention
Build these guardrails in once so later batches do not depend on manual checks.
- Create a CloudWatch alarm on Bedrock's
InvocationThrottlesand client-error metrics, so throttling shows up before editors report it. - Attach an n8n Error Trigger workflow that posts failed execution IDs and node names to a chat channel.
- Keep model IDs and inference profiles in configuration, not hard-coded in nodes, so a deprecation is a one-line change.
- Export workflows as JSON to Git, and review changes like code.
- Set an AWS Budgets alert on Bedrock spend. A looping retry bug is also a billing bug.
Pre-launch checklist for every new workflow:
[ ] Batch size and Wait set; tested with 20+ items, not 1
[ ] Retry On Fail enabled; SDK maxAttempts/backoff configured
[ ] Webhook responds immediately; job ID returned
[ ] Idempotency key claimed atomically before generation
[ ] stopReason and schema validated before publish
[ ] Output lands as draft; human approval required
[ ] IAM scoped to the specific model/profile ARNs
[ ] Alarms: throttles, errors, spend
[ ] Failure test: kill a worker mid-run, resend webhook, confirm no duplicates
The last item matters most. Duplicate and half-finished runs only appear when something fails on purpose. For more runbooks on automation and cloud tooling, browse kuryzhev.cloud, and check the Amazon Bedrock quotas documentation whenever you add a model or region.
Top comments (0)