Building a Claude Agent That Tails Your CloudFormation Lambdas

If you’ve spent any time developing against a deployed CloudFormation stack, you know the loop. You make a change locally, trigger an action, then alt-tab to a terminal running aws logs tail. You squint at the output. Maybe you open CloudWatch to get more context. You find the error, alt-tab back, fix, repeat. It’s death by context switching.

I wanted something better. What if Claude could just watch my Lambdas and tell me what’s happening?

The Problem

I’m developing a system locally that talks to deployed AWS infrastructure. My Lambdas run in the cloud and their logs live in CloudWatch. Getting useful signal means:

  1. Figuring out which Lambda I care about (not obvious when the stack has 15 of them)
  2. Tailing the right log group
  3. Reading CloudWatch’s raw output to find the actual error
  4. Mentally mapping the error back to my local code

That’s a lot of overhead when all I really want to know is “did my thing work?”

The Idea

Claude Code supports custom slash commands and can run bash commands. What if I wrote a slash command that:

  1. Discovers all Lambdas in a given CloudFormation stack
  2. Tails their CloudWatch log groups
  3. Feeds the log output to Claude
  4. Reports errors, warnings, and progress in plain English

An agent that sits in my terminal, watches my stack, and tells me what’s going on.

Implementation

Step 1: Discover the Lambdas

First I need to find all Lambda functions that belong to my CloudFormation stack. AWS gives us list-stack-resources.

aws cloudformation list-stack-resources \
  --stack-name my-stack \
  --query "StackResourceSummaries[?ResourceType=='AWS::Lambda::Function'].PhysicalResourceId" \
  --output text

This returns the physical resource IDs (the actual Lambda function names).

Step 2: Map Lambdas to Log Groups

CloudWatch log groups follow a predictable pattern:

/aws/lambda/<function-name>

Mapping is straightforward. Just prepend /aws/lambda/ to each function name.

Step 3: Tail the Logs

AWS CLI v2 has aws logs tail which supports --follow for live tailing.

aws logs tail "/aws/lambda/my-function" --follow --since 5m --format short

The --since 5m flag is key. I don’t want the entire history, just what’s happening now.

Step 4: The Agent Script

I put together a bash script that ties it all together. A Claude Code slash command invokes it.

#!/bin/bash
# .claude/scripts/tail-stack.sh
 
STACK_NAME="${1:-my-default-stack}"
SINCE="${2:-5m}"
REGION="${3:-us-east-1}"
 
# Discover all Lambda functions
FUNCTIONS=$(aws cloudformation list-stack-resources \
  --stack-name "$STACK_NAME" \
  --region "$REGION" \
  --query "StackResourceSummaries[?ResourceType=='AWS::Lambda::Function'].PhysicalResourceId" \
  --output text)
 
if [ -z "$FUNCTIONS" ]; then
  echo "No Lambda functions found in stack: $STACK_NAME"
  exit 1
fi
 
echo "=== Monitoring Lambda functions in stack: $STACK_NAME ==="
echo ""
 
for FUNC in $FUNCTIONS; do
  LOG_GROUP="/aws/lambda/$FUNC"
  echo "--- Tailing: $FUNC ---"
  echo "Log group: $LOG_GROUP"
  echo ""
 
  # Fetch recent logs (non-follow mode for agent consumption)
  LOGS=$(aws logs tail "$LOG_GROUP" \
    --region "$REGION" \
    --since "$SINCE" \
    --format short 2>&1)
 
  if [ -z "$LOGS" ]; then
    echo "(no recent log events)"
  else
    echo "$LOGS"
  fi
 
  echo ""
  echo "---"
  echo ""
done

Step 5: The Slash Command

Claude Code lets you define custom slash commands as markdown prompt files. I created .claude/commands/tail-stack.md:

Run the Lambda log tailing script and analyze the output.
 
Execute this command:

bash .claude/scripts/tail-stack.sh $ARGUMENTS


After getting the logs, do the following:

1. **Identify Errors**: Look for any ERROR, exception, timeout, or failure patterns. Explain what likely caused each one and suggest a fix.

2. **Trace Request Flow**: If multiple Lambdas were involved, piece together the execution flow across functions. Use request IDs or correlation IDs to connect related log entries.

3. **Flag Warnings**: Call out any warnings, slow invocations (duration > 3s), cold starts, or near-timeout executions.

4. **Summarize**: Give a brief status. Is the stack healthy? What's failing? What should I look at first?

Keep the analysis concise and actionable. I want to know what's broken and what to do about it, not a verbose restatement of the logs.

Now I can run /tail-stack my-stack-name and get an instant analysis.

Making It Smarter: Continuous Monitoring

The slash command works well for one-off checks but I really wanted continuous monitoring. Claude Code hooks let you run scripts when events fire, like after every tool call or notification.

I set up a lightweight polling approach using a background process:

#!/bin/bash
# .claude/scripts/watch-stack.sh
 
STACK_NAME="${1:-my-default-stack}"
LOG_FILE="/tmp/stack-watcher-${STACK_NAME}.log"
PID_FILE="/tmp/stack-watcher-${STACK_NAME}.pid"
 
# Get all log groups
FUNCTIONS=$(aws cloudformation list-stack-resources \
  --stack-name "$STACK_NAME" \
  --query "StackResourceSummaries[?ResourceType=='AWS::Lambda::Function'].PhysicalResourceId" \
  --output text)
 
LOG_GROUPS=""
for FUNC in $FUNCTIONS; do
  LOG_GROUPS="$LOG_GROUPS /aws/lambda/$FUNC"
done
 
# Poll logs every 30 seconds
echo $$ > "$PID_FILE"
while true; do
  for LG in $LOG_GROUPS; do
    aws logs tail "$LG" --since 30s --format short 2>/dev/null >> "$LOG_FILE"
  done
  sleep 30
done

Then a companion slash command reads the accumulated log file and analyzes it:

Check the latest Lambda logs from the background watcher.
 
Read the contents of /tmp/stack-watcher-$ARGUMENTS.log, analyze any new errors or issues, then truncate the file.
 
Focus on:
- New errors since last check
- Pattern changes (is something failing repeatedly?)
- Cold starts or performance degradation
 
After analysis, run: `> /tmp/stack-watcher-$ARGUMENTS.log` to clear the processed logs.

What This Looks Like

Here’s a typical interaction:

> /tail-stack sensei-api-stack

Analyzing logs for 6 Lambda functions in sensei-api-stack...

✓ SenseiAuthFunction — Healthy. 12 invocations, avg 45ms.

✗ SenseiEvalFunction — ERROR in 3 recent invocations:
  "Cannot read properties of undefined (reading 'evaluationCriteria')"
  → The event payload is missing the evaluationCriteria field.
    Check your request body — likely a schema mismatch between
    your local client and the deployed function.

⚠ SenseiGenerateFunction — Warning: 2 cold starts detected,
  one invocation took 8.2s (timeout is 15s). Consider provisioned
  concurrency if this is user-facing.

✓ SenseiDiscordBotFunction — Healthy. 45 invocations, avg 120ms.
✓ SenseiStorageFunction — Healthy. No recent invocations.
✓ SenseiSchedulerFunction — Healthy. 1 scheduled invocation, success.

Summary: The eval function is broken — schema mismatch is your
top priority. Everything else looks good.

That’s the feedback loop I wanted. No context switching. No squinting at CloudWatch. No trying to remember which Lambda handles what. Just “here’s what’s broken, here’s why, here’s what to fix.”

Filtering the Noise

CloudWatch logs are noisy. Lambda execution reports, START/END markers, SDK debug output. Most of it isn’t useful while actively developing. I added a filter to the tailing script:

LOGS=$(aws logs tail "$LOG_GROUP" \
  --region "$REGION" \
  --since "$SINCE" \
  --format short 2>&1 | \
  grep -v "^START\|^END\|^REPORT\|INIT_START\|^$")

This strips the framework noise and leaves just the application logs. Claude gets cleaner input which means cleaner analysis.

Multi-Stack Support

Most real projects have multiple stacks. Maybe an API stack, a data pipeline stack, and an auth stack. The script handles this naturally since you pass the stack name as an argument. I also added a convenience command that monitors everything:

#!/bin/bash
# .claude/scripts/tail-all-stacks.sh
 
STACKS=("api-stack" "data-pipeline-stack" "auth-stack")
 
for STACK in "${STACKS[@]}"; do
  echo "=========================================="
  echo "STACK: $STACK"
  echo "=========================================="
  bash .claude/scripts/tail-stack.sh "$STACK" "5m"
  echo ""
done

Lessons Learned

Claude is genuinely good at log analysis. This isn’t just a fancy grep for “ERROR”. Claude correlates events across functions, identifies root causes vs symptoms, and suggests relevant fixes. When it sees a DynamoDB ConditionalCheckFailedException in one Lambda and a missing item error in another, it connects the dots.

The slash command approach beats a full MCP server here. I initially considered building an MCP server that gives Claude direct CloudWatch access. That’s the right architecture if you need Claude to interactively query logs, ask follow-up questions, drill into specific time ranges. But I just want “tell me what’s happening” and a script that dumps logs plus a prompt that says “analyze this” is simpler and works great.

Filtering matters more than you’d think. The first version sent raw CloudWatch output to Claude, including all the REPORT lines with billed duration and memory stats. Claude would dutifully analyze all of it, telling me about memory utilization patterns when all I wanted to know was why my API was returning 500s. Stripping the noise made the analysis dramatically more useful.

Time windows need tuning. The --since 5m default works when I’m actively developing and triggering actions frequently. Background monitoring needs a shorter window (30s) polled more often. Debugging something that happened “a while ago” might need --since 1h or more. Making this configurable was worth the effort.

What’s Next

I want to extend this so it catches issues before I even notice them. The background watcher could trigger a notification when it detects a new error pattern. Instead of me asking “what’s happening?”, Claude could proactively say “hey, your auth function started throwing 403s two minutes ago.”

The infrastructure exists in Claude Code’s hook system. It’s just a matter of wiring it up. That might be a post for another day.


If you’re developing against deployed AWS infrastructure and spending too much time in CloudWatch, try this pattern. The setup takes about 15 minutes and the payoff is immediate. Your Lambda logs become a conversation instead of a chore.