{"slug": "how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda", "title": "How I Solved Claude Code Silently Missing the SQS Trigger on My Lambda", "summary": "A developer detailed how Claude Code silently introduced a bug by guessing the event shape for an SQS-triggered Lambda, accessing `event.body` instead of the correct `event.Records[0].body`. The developer then used the Infrawise MCP server to provide Claude Code with accurate infrastructure context—trigger type, queue details, and table schema—eliminating guesswork and catching issues like missing dead-letter queues before deployment.", "body_md": "I asked Claude Code to add a handler for `processOrders`\n\n, a Lambda wired up to consume from `orders-queue`\n\nand read the matching row out of the `Orders`\n\nDynamoDB table. It wrote one immediately — confidently, in one shot, the way it writes most things.\n\nIt also parsed the event body wrong.\n\n`processOrders`\n\nis invoked by `orders-queue`\n\nthrough an event source mapping — that's not in `processOrders`\n\n's source file, it's in the AWS account. Claude Code, reading only the handler's code, had no way to see that mapping. So it guessed at the event shape instead of knowing it, and reached for `event.body`\n\n— the shape you'd expect from an API Gateway proxy integration, not from SQS. The actual shape for an SQS-triggered Lambda is `event.Records[0].body`\n\n. First message in, first field access, and the handler throws.\n\nThat wasn't the only thing invisible from source alone:\n\n`orders-queue`\n\nhas no dead-letter queue. If the handler throws, SQS retries up to `maxReceiveCount`\n\ntimes and then discards the message — no alert, no queue-depth spike, nothing in the logs pointing at what happened. A failed order just vanishes.`Orders`\n\ntable has one partition key (`orderId`\n\n) and zero GSIs. Any lookup by anything else becomes a full table scan the moment the handler ships.None of that is visible from reading `processOrders`\n\n's current code. It's the state of the AWS account, and an AI assistant reading only source files fills the gap with a plausible guess instead of a fact — starting with which trigger the function even has.\n\nHere's what the same session looked like once I wired [infrawise](https://github.com/Sidd27/infrawise) into Claude Code as an MCP server and asked it to redo the handler.\n\n**Start wide: get_infra_overview.** No arguments, just a snapshot — table names, queue names, lambda count, high-severity findings. It comes back showing the\n\n`Orders`\n\ntable, `orders-queue`\n\n, and `processOrders`\n\nalready in the graph, plus a high-severity finding already attached to that Lambda. That finding surfaces before the handler is written, not after the deploy.**Narrow to the function: analyze_function.** Called with\n\n`function: \"processOrders\"`\n\n, this is the tool built for \"I'm about to write or review this handler,\" and it returns four things Claude Code would otherwise guess at:`processOrders`\n\nis triggered by SQS, so the correct shape is `event.Records[0].body`\n\n— not `event.body`\n\n. Infrawise keeps a fixed map per trigger type (SNS is `event.Records[0].Sns.Message`\n\n, S3 is `event.Records[0].s3.object.key`\n\n, DynamoDB Streams is `event.Records[0].dynamodb.NewImage`\n\n), so the entry point is right on the first draft.`LambdaMissingTriggerDLQAnalyzer`\n\nwalks every Lambda's SQS/Kinesis/DynamoDB-stream triggers, checks whether the source queue has a DLQ, and raises high severity when it doesn't: `analyze_function`\n\ndiffs what the function's code actually calls against what its role allows, and returns the gap — catching an `AccessDeniedException`\n\nbefore it happens in prod instead of after.**Confirm the queue directly: get_queue_details.** The handler's retry behavior depends on more than DLQ presence, so Claude Code checks the queue itself:\n\n`hasDLQ: false`\n\nconfirmed, plus visibility timeout, encryption, and `isFifo`\n\n. That last field matters more than it looks — if the queue were FIFO, every `SendMessage`\n\ncall in the handler would need a `MessageGroupId`\n\n, and skipping it isn't a lint warning, it's a runtime error the first time the handler publishes.**Get the schema right: get_table_schema.** Called with\n\n`tables: [\"Orders\"]`\n\n, this returns the partition key (`orderId`\n\n), billing mode (`PAY_PER_REQUEST`\n\n), and the fact that there are no GSIs. If the handler needs to look up an order by anything other than `orderId`\n\n, that's a `Scan`\n\n, and infrawise's scan analyzer flags it the moment it lands in the code graph — so Claude Code reaches for `suggest_gsi`\n\nup front instead of shipping the scan and fixing it in the next PR. Row data is never included — column names, types, keys, and index definitions only, and only for the tables this handler actually touches, not the whole account's schema dumped into the prompt.With all four calls back, the rewritten handler:\n\n`event.Records[0].body`\n\n— the correct shape for this trigger, not a guess`Orders`\n\nby `orderId`\n\n— no scan, because the schema call already ruled that out`orders-queue`\n\nneeds a DLQ before this goes to production`isFifo`\n\nfirstEvery one of those four things was either wrong or invisible in the first version — the one Claude Code wrote from source files alone. None of it required me to open the AWS console mid-task or remember which table has which GSI.\n\nThe four tools compose in a fixed order because each answers a narrower question than the last: `get_infra_overview`\n\nsays what exists, `analyze_function`\n\nsays what's wrong with the one thing being touched, `get_queue_details`\n\nand `get_table_schema`\n\nfill in the operational and schema detail neither of the first two carries at full resolution. That's the same shape as infrawise's own \"reviewing an entire service\" and \"before writing a query\" patterns — it holds for any handler, not just this one.\n\nNone of this runs an LLM against the infrastructure. Extraction and analysis are deterministic — AST parsing, live AWS reads, rule-based analyzers, graph correlation. Claude Code only consumes that graph through MCP, the same way it consumes source files. The difference is the graph doesn't guess.\n\nTry it in your own project — `npx infrawise start --claude`\n\nwrites the `.mcp.json`\n\nand opens Claude Code with all 21 tools wired in, no server to babysit. [GitHub](https://github.com/Sidd27/infrawise) · [npm](https://www.npmjs.com/package/infrawise)\n\n`analyze_function`\n\nbefore writing or reviewing any Lambda handler — it returns the correct trigger event shape, missing IAM permissions, and any DLQ/trigger findings in one call.`maxReceiveCount`\n\ntimes, then discarded with no record.`isFifo`\n\non any queue before writing `SendMessage`\n\ncalls — a missing `MessageGroupId`\n\non a FIFO queue is a runtime error, not a style issue.`get_table_schema`\n\nwith just the tables a handler touches instead of dumping the whole schema into the prompt — it also tells you whether the GSI your query needs already exists.", "url": "https://wpnews.pro/news/how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda", "canonical_source": "https://dev.to/siddharth_pandey_27/how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda-5a", "published_at": "2026-07-22 19:50:35+00:00", "updated_at": "2026-07-22 20:01:37.298651+00:00", "lang": "en", "topics": ["artificial-intelligence", "developer-tools", "ai-agents"], "entities": ["Claude Code", "Infrawise", "AWS", "SQS", "Lambda", "DynamoDB"], "alternates": {"html": "https://wpnews.pro/news/how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda", "markdown": "https://wpnews.pro/news/how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda.md", "text": "https://wpnews.pro/news/how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda.txt", "jsonld": "https://wpnews.pro/news/how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda.jsonld"}}