# How I Solved Claude Code Silently Missing the SQS Trigger on My Lambda

> Source: <https://dev.to/siddharth_pandey_27/how-i-solved-claude-code-silently-missing-the-sqs-trigger-on-my-lambda-5a>
> Published: 2026-07-22 19:50:35+00:00

I asked Claude Code to add a handler for `processOrders`

, a Lambda wired up to consume from `orders-queue`

and read the matching row out of the `Orders`

DynamoDB table. It wrote one immediately — confidently, in one shot, the way it writes most things.

It also parsed the event body wrong.

`processOrders`

is invoked by `orders-queue`

through an event source mapping — that's not in `processOrders`

'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`

— 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`

. First message in, first field access, and the handler throws.

That wasn't the only thing invisible from source alone:

`orders-queue`

has no dead-letter queue. If the handler throws, SQS retries up to `maxReceiveCount`

times 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`

table has one partition key (`orderId`

) 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`

'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.

Here'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.

**Start wide: get_infra_overview.** No arguments, just a snapshot — table names, queue names, lambda count, high-severity findings. It comes back showing the

`Orders`

table, `orders-queue`

, and `processOrders`

already 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

`function: "processOrders"`

, 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`

is triggered by SQS, so the correct shape is `event.Records[0].body`

— not `event.body`

. Infrawise keeps a fixed map per trigger type (SNS is `event.Records[0].Sns.Message`

, S3 is `event.Records[0].s3.object.key`

, DynamoDB Streams is `event.Records[0].dynamodb.NewImage`

), so the entry point is right on the first draft.`LambdaMissingTriggerDLQAnalyzer`

walks 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`

diffs what the function's code actually calls against what its role allows, and returns the gap — catching an `AccessDeniedException`

before 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:

`hasDLQ: false`

confirmed, plus visibility timeout, encryption, and `isFifo`

. That last field matters more than it looks — if the queue were FIFO, every `SendMessage`

call in the handler would need a `MessageGroupId`

, 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

`tables: ["Orders"]`

, this returns the partition key (`orderId`

), billing mode (`PAY_PER_REQUEST`

), and the fact that there are no GSIs. If the handler needs to look up an order by anything other than `orderId`

, that's a `Scan`

, and infrawise's scan analyzer flags it the moment it lands in the code graph — so Claude Code reaches for `suggest_gsi`

up 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:

`event.Records[0].body`

— the correct shape for this trigger, not a guess`Orders`

by `orderId`

— no scan, because the schema call already ruled that out`orders-queue`

needs a DLQ before this goes to production`isFifo`

firstEvery 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.

The four tools compose in a fixed order because each answers a narrower question than the last: `get_infra_overview`

says what exists, `analyze_function`

says what's wrong with the one thing being touched, `get_queue_details`

and `get_table_schema`

fill 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.

None 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.

Try it in your own project — `npx infrawise start --claude`

writes the `.mcp.json`

and 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)

`analyze_function`

before 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`

times, then discarded with no record.`isFifo`

on any queue before writing `SendMessage`

calls — a missing `MessageGroupId`

on a FIFO queue is a runtime error, not a style issue.`get_table_schema`

with 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.
