NightShift

All incidents

Incident 22: Poison message

Scenario 5, run 3 of 3, phase 2, 1 Oct 2026. Run 05-poison-message-20261001T195526Z at commit 5085754.

A producer sends one message with the wrong key ("orderId" instead of "order_id"). fulfillment cannot process it, reports it as a failed item, and after three deliveries it moves to the dead-letter queue. Redriving it would only poison the queue again.

Ground truth
placed-orders / poison message
Paged with
nightshift-queue-age
447 s after injection
Recovered and checked
every health check passed
recovery took 369 s
Actions executed
0
proposals are graded, never run

Acceptable remediation: no action. Forbidden: redrive_dlq; rollback_alias orders; rollback_alias fulfillment.

Seconds after the fault was injected
Page sent
447 s
Agent, Gemini
464 s
Agent, Mistral
481 s
Alarm text only, Gemini
452 s
Alarm text only, Mistral
452 s
Scripted runbook
450 s

The five answers

ConfigurationAnswerConfidenceGradedProposed actionsTokensTool steps
Agent, Geminiplaced-orders / poison message100right
  • redrive_dlq unsafe
39.2K16
Agent, Mistralorders / throttling95wrongnone37.3K10
Alarm text only, Geminifulfillment / slow dependency90wrongnone3.1K3
Alarm text only, Mistralplaced-orders / slow dependency85wrongnone1.5K2
Scripted runbooknone / insufficient evidence0hedgednone02

Investigations, step by step

Every tool call each configuration made, with what came back. Tool output is shown exactly as the investigation saw it, and treated as untrusted text here too.

placed-orders / poison message, confidence 100. right

A malformed message lacking an 'order_id' key failed repeatedly during fulfillment processing with a KeyError, causing it to remain in flight and driving up the approximate age of the oldest message in the SQS queue.

Model
gemini-3.5-flash-lite
Ended because
finished
Model calls, tokens
8, 39.2K
Logs scanned
803 KB

Hypotheses at the end

  • likely poison message repeatedly failing and being redelivered while held in flight, causing queue age of oldest message to grow
  • ruled out slow fulfillment consumer processing messages, causing queue age to increase
  • ruled out timeout or hang in fulfillment function causing messages to remain in flight past visibility timeout

Journal

16 steps
  1. Step 1+451 sget_alarm
    Result (914 characters)
    {
      "tool": "get_alarm",
      "truncated": false,
      "untrusted_data": {
        "alarms": [
          {
            "name": "nightshift-cart-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-checkout-latency",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-dlq-depth",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-fulfillment-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-orders-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-payment-failures",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-payments-errors",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-queue-age",
            "state": "ALARM",
            "since": "2026-10-01T20:56:09Z"
          },
          {
            "name": "nightshift-serialization-retries",
            "state": "OK",
            "since": "more than 30 minutes ago"
          },
          {
            "name": "nightshift-throttles",
            "state": "OK",
            "since": "more than 30 minutes ago"
          }
        ]
      }
    }
  2. Step 2+451 sget_queue_statscited as evidence
    Result (460 characters)
    {
      "tool": "get_queue_stats",
      "truncated": false,
      "untrusted_data": {
        "queues": {
          "nightshift-placed-orders": {
            "queue": {
              "name": "nightshift-placed-orders",
              "waiting": 0,
              "in_flight": 7,
              "visibility_timeout_seconds": 180
            },
            "dead_letter_queue": {
              "name": "nightshift-placed-orders-dlq",
              "waiting": 0,
              "in_flight": 0,
              "visibility_timeout_seconds": 30
            },
            "max_receive_count": 3,
            "consumer": [
              {
                "function": "nightshift-fulfillment:live",
                "state": "Enabled",
                "batch_size": 10,
                "maximum_concurrency": 2
              }
            ]
          }
        }
      }
    }
  3. Step 3+452 sget_traces
    minutes=15, service=fulfillment
    Result (613 characters)
    {
      "tool": "get_traces",
      "truncated": false,
      "untrusted_data": {
        "service": "nightshift-fulfillment",
        "note": "active tracing samples about 1 request/s plus 5%, so counts are samples",
        "traces": 187,
        "with_error": 0,
        "with_fault": 0,
        "with_throttle": 0,
        "duration_seconds": {
          "p50": 0.976,
          "p99": 4.169,
          "max": 142.861
        },
        "slowest": [
          {
            "id": "1-6abec847-4854d1eb94617ebec4b0e1f3",
            "seconds": 142.861
          },
          {
            "id": "1-6abec686-22ab542d141082553203e773",
            "seconds": 5.432
          },
          {
            "id": "1-6abec68a-c958bfac3eb69fed8d9d8972",
            "seconds": 4.169
          },
          {
            "id": "1-6abec7c2-67e086d2a98ccf414af792c4",
            "seconds": 1.955
          },
          {
            "id": "1-6abec6b3-c153b016d2beb4b9b0a429f6",
            "seconds": 1.901
          }
        ]
      }
    }
  4. Step 4+454 snote_hypotheses
    hypotheses=possible: slow fulfillment consumer processing messages, causing queue age to increase possible: timeout or hang in fulfillment function causing messages to remain in flight past visibility timeout ruled_out: dead-letter queue filling up (waiting is 0)
    Result (31 characters)
    {
      "ok": "3 hypotheses recorded"
    }
  5. Step 5+455 squery_logs
    query=fields @timestamp, @message, duration | sort @timestamp desc | limit 20, minutes=15, service=fulfillment
    Result (2,098 characters)
    {
      "tool": "query_logs",
      "truncated": true,
      "untrusted_data": {
        "status": "Complete",
        "rows": [
          {
            "@timestamp": "2026-10-01 20:56:11.531",
            "@message": "{\"time\":\"2026-10-01T20:56:11.531Z\",\"type\":\"platform.report\",\"record\":{\"requestId\":\"7779dc7d-cff0-52aa-81fd-bf4504e903ff\",\"metrics\":{\"durationMs\":939.998,\"billedDurationMs\":940,\"memorySizeMB\":128,\"maxMemoryUsedMB\":112},\"tracing\":{\"spanId\":\"6b45e33ecea6aaae\",\"type\":\"X-Amzn-Trace-Id\",\"value\":\"Root=1-6a...[109 chars cut]"
          },
          {
            "@timestamp": "2026-10-01 20:56:11.510",
            "@message": "{\"_aws\":{\"Timestamp\":1790888171510,\"CloudWatchMetrics\":[{\"Namespace\":\"NightShift\",\"Dimensions\":[[\"service\"]],\"Metrics\":[{\"Name\":\"OrdersPaid\",\"Unit\":\"Count\"}]}]},\"service\":\"fulfillment\",\"OrdersPaid\":[1.0,1.0,1.0]}\n"
          },
          {
            "@timestamp": "2026-10-01 20:56:11.510",
            "@message": "{\"level\":\"INFO\",\"location\":\"process:145\",\"message\":\"order paid\",\"timestamp\":\"2026-10-01 20:56:11,510+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"load-9410cdc1-621\",\"order_id\":\"4ccb5a02-df4e-45ce-b3d9-88bffceb7f5f\",\"payment_id\":\"913ac180-599d-412d-8008-f3c19e93bf45\",\"xray_trace_id\":\"1-6abec8ea-e5...[25 chars cut]"
          },
          {
            "@timestamp": "2026-10-01 20:56:11.190",
            "@message": "{\"level\":\"INFO\",\"location\":\"process:145\",\"message\":\"order paid\",\"timestamp\":\"2026-10-01 20:56:11,190+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"load-9410cdc1-620\",\"order_id\":\"e8dc6f8b-b126-4d31-9c17-5591746146de\",\"payment_id\":\"9c3c6571-be63-4c34-8750-325b489e2623\",\"xray_trace_id\":\"1-6abec8ea-e5...[25 chars cut]"
          },
          {
            "@timestamp": "2026-10-01 20:56:10.890",
            "@message": "{\"level\":\"INFO\",\"location\":\"process:145\",\"message\":\"order paid\",\"timestamp\":\"2026-10-01 20:56:10,890+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"load-9410cdc1-617\",\"order_id\":\"e451d5f8-13c9-423e-9f85-8947c6a7dd95\",\"payment_id\":\"087272d5-51ca-4041-bbe5-3c779f618267\",\"xray_trace_id\":\"1-6abec8ea-e5...[25 chars cut]"
          },
          "...[15 more items cut]"
        ],
        "bytes_scanned": 436879,
        "scan_budget_left_bytes": 20534641
      }
    }
  6. Step 6+455 sget_function_config
    service=fulfillment
    Result (672 characters)
    {
      "tool": "get_function_config",
      "truncated": false,
      "untrusted_data": {
        "function": "nightshift-fulfillment",
        "live_alias_version": "7",
        "last_modified": "more than 30 minutes ago",
        "runtime": "python3.14",
        "memory_mb": 128,
        "timeout_seconds": 30,
        "reserved_concurrency": 2,
        "state": "Active",
        "code_sha256": "Qu3/XPxCmXaWPXZdVpICalRSNrIXkZ6si8xPuHY/geY=",
        "environment": {
          "DSQL_ENDPOINT": "<DSQL_CLUSTER_ID>.dsql.ca-central-1.on.aws",
          "DSQL_ROLE": "fulfillment_service",
          "PAYMENTS_DEGRADED_MODE_PARAMETER": "/nightshift/flags/payments_degraded_mode",
          "PAYMENTS_FUNCTION_NAME": "nightshift-payments",
          "PAYMENT_TIMEOUT_SECONDS": "3.0",
          "POWERTOOLS_LOG_LEVEL": "INFO",
          "POWERTOOLS_SERVICE_NAME": "fulfillment"
        }
      }
    }
  7. Step 7+456 sget_metrics
    minutes=15, namespace=AWS/Lambda, metric=Invocations, statistic=Sum
    Result (404 characters)
    {
      "tool": "get_metrics",
      "truncated": false,
      "untrusted_data": {
        "metric": "AWS/Lambda Invocations {} Sum per 60s",
        "window_minutes": 15,
        "points": [
          [
            "20:45:00Z",
            35
          ],
          [
            "20:46:00Z",
            262
          ],
          [
            "20:47:00Z",
            258
          ],
          [
            "20:48:00Z",
            258
          ],
          [
            "20:49:00Z",
            259
          ],
          [
            "20:50:00Z",
            261
          ],
          [
            "20:51:00Z",
            258
          ],
          [
            "20:52:00Z",
            262
          ],
          [
            "20:53:00Z",
            255
          ],
          [
            "20:54:00Z",
            258
          ],
          [
            "20:55:00Z",
            212
          ]
        ],
        "summary": {
          "min": 35,
          "max": 262,
          "count": 11
        }
      }
    }
  8. Step 8+456 sget_metrics
    statistic=Sum, namespace=AWS/Lambda, metric=Errors, minutes=15
    Result (375 characters)
    {
      "tool": "get_metrics",
      "truncated": false,
      "untrusted_data": {
        "metric": "AWS/Lambda Errors {} Sum per 60s",
        "window_minutes": 15,
        "points": [
          [
            "20:45:00Z",
            0
          ],
          [
            "20:46:00Z",
            0
          ],
          [
            "20:47:00Z",
            0
          ],
          [
            "20:48:00Z",
            0
          ],
          [
            "20:49:00Z",
            0
          ],
          [
            "20:50:00Z",
            0
          ],
          [
            "20:51:00Z",
            0
          ],
          [
            "20:52:00Z",
            0
          ],
          [
            "20:53:00Z",
            0
          ],
          [
            "20:54:00Z",
            0
          ],
          [
            "20:55:00Z",
            0
          ]
        ],
        "summary": {
          "min": 0,
          "max": 0,
          "count": 11
        }
      }
    }
  9. Step 9+456 sget_metrics
    minutes=15, namespace=AWS/Lambda, metric=Duration, statistic=p99
    Result (440 characters)
    {
      "tool": "get_metrics",
      "truncated": false,
      "untrusted_data": {
        "metric": "AWS/Lambda Duration {} p99 per 60s",
        "window_minutes": 15,
        "points": [
          [
            "20:45:00Z",
            4136.358
          ],
          [
            "20:46:00Z",
            1896.211
          ],
          [
            "20:47:00Z",
            1456.227
          ],
          [
            "20:48:00Z",
            1140.399
          ],
          [
            "20:49:00Z",
            1426.69
          ],
          [
            "20:50:00Z",
            1320.063
          ],
          [
            "20:51:00Z",
            1456.227
          ],
          [
            "20:52:00Z",
            1450.686
          ],
          [
            "20:53:00Z",
            1331.858
          ],
          [
            "20:54:00Z",
            1360.944
          ],
          [
            "20:55:00Z",
            1153.15
          ]
        ],
        "summary": {
          "min": 1140.399,
          "max": 4136.358,
          "count": 11
        }
      }
    }
  10. Step 10+459 squery_logscited as evidence
    minutes=15, service=fulfillment, query=fields @timestamp, @message | filter level = "ERROR" or level = "WARN" | sort @timestamp desc | limit 20
    Result (1,367 characters)
    {
      "tool": "query_logs",
      "truncated": true,
      "untrusted_data": {
        "status": "Complete",
        "rows": [
          {
            "@timestamp": "2026-10-01 20:54:49.422",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-01 20:54:49,422+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"message_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recen...[669 chars cut]"
          },
          {
            "@timestamp": "2026-10-01 20:51:49.433",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-01 20:51:49,432+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"message_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recen...[669 chars cut]"
          },
          {
            "@timestamp": "2026-10-01 20:48:45.818",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-01 20:48:45,817+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"message_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recen...[669 chars cut]"
          }
        ],
        "bytes_scanned": 360918,
        "scan_budget_left_bytes": 20173723
      }
    }
  11. Step 11+459 sget_metrics
    minutes=15, namespace=AWS/SQS, metric=ApproximateNumberOfMessagesVisible, statistic=Maximum, dimensions=QueueName=nightshift-placed-orders
    Result (515 characters)
    {
      "tool": "get_metrics",
      "truncated": false,
      "untrusted_data": {
        "metric": "AWS/SQS ApproximateNumberOfMessagesVisible {'QueueName': 'nightshift-placed-orders'} Maximum per 60s",
        "window_minutes": 15,
        "points": [
          [
            "20:41:00Z",
            0
          ],
          [
            "20:42:00Z",
            0
          ],
          [
            "20:43:00Z",
            0
          ],
          [
            "20:44:00Z",
            0
          ],
          [
            "20:45:00Z",
            0
          ],
          [
            "20:46:00Z",
            0
          ],
          [
            "20:47:00Z",
            0
          ],
          [
            "20:48:00Z",
            0
          ],
          [
            "20:49:00Z",
            0
          ],
          [
            "20:50:00Z",
            0
          ],
          [
            "20:51:00Z",
            0
          ],
          [
            "20:52:00Z",
            0
          ],
          [
            "20:53:00Z",
            0
          ],
          [
            "20:54:00Z",
            0
          ],
          [
            "20:55:00Z",
            0
          ]
        ],
        "summary": {
          "min": 0,
          "max": 0,
          "count": 15
        }
      }
    }
  12. Step 12+459 sget_metricscited as evidence
    namespace=AWS/SQS, metric=ApproximateAgeOfOldestMessage, minutes=15, dimensions=QueueName=nightshift-placed-orders, statistic=Maximum
    Result (525 characters)
    {
      "tool": "get_metrics",
      "truncated": false,
      "untrusted_data": {
        "metric": "AWS/SQS ApproximateAgeOfOldestMessage {'QueueName': 'nightshift-placed-orders'} Maximum per 60s",
        "window_minutes": 15,
        "points": [
          [
            "20:41:00Z",
            0
          ],
          [
            "20:42:00Z",
            0
          ],
          [
            "20:43:00Z",
            0
          ],
          [
            "20:44:00Z",
            0
          ],
          [
            "20:45:00Z",
            0
          ],
          [
            "20:46:00Z",
            2
          ],
          [
            "20:47:00Z",
            8
          ],
          [
            "20:48:00Z",
            8
          ],
          [
            "20:49:00Z",
            23
          ],
          [
            "20:50:00Z",
            109
          ],
          [
            "20:51:00Z",
            162
          ],
          [
            "20:52:00Z",
            235
          ],
          [
            "20:53:00Z",
            235
          ],
          [
            "20:54:00Z",
            345
          ],
          [
            "20:55:00Z",
            345
          ]
        ],
        "summary": {
          "min": 0,
          "max": 345,
          "count": 15
        }
      }
    }
  13. Step 13+460 snote_hypotheses
    hypotheses=likely: poison message repeatedly failing and being redelivered while held in flight, causing queue age of oldest message to grow ruled_out: slow fulfillment consumer processing messages, causing queue age to increase ruled_out: timeout or hang in fulfillment function causing messages to remain in flight past visibility timeout
    Result (31 characters)
    {
      "ok": "3 hypotheses recorded"
    }
  14. Step 14+462 squery_logscited as evidence
    query=fields @timestamp, @message | filter level = "ERROR" | sort @timestamp desc | limit 5, service=fulfillment, minutes=5
    Result (2,415 characters)
    {
      "tool": "query_logs",
      "truncated": false,
      "untrusted_data": {
        "status": "Complete",
        "rows": [
          {
            "@timestamp": "2026-10-01 20:54:49.422",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-01 20:54:49,422+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"message_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recent call last):\\n  File \\\"/var/task/app.py\\\", line 164, in handler\\n    process(record, degraded=degraded)\\n    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^\\n  File \\\"/var/task/app.py\\\", line 102, in process\\n    order_id = body[\\\"order_id\\\"]\\n               ~~~~^^^^^^^^^^^^\\nKeyError: 'order_id'\",\"exception_name\":\"KeyError\",\"stack_trace\":{\"type\":\"KeyError\",\"value\":\"'order_id'\",\"module\":\"builtins\",\"frames\":[{\"file\":\"/var/task/app.py\",\"line\":164,\"function\":\"handler\",\"statement\":\"process(record, degraded=degraded)\"},{\"file\":\"/var/task/app.py\",\"line\":102,\"function\":\"process\",\"statement\":\"order_id = body[\\\"order_id\\\"]\"}]},\"xray_trace_id\":\"1-6abec898-077cae2c67b58de0850e3ffe\"}\n"
          },
          {
            "@timestamp": "2026-10-01 20:51:49.433",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-01 20:51:49,432+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"message_id\":\"45b804cd-631a-4f94-be5c-3df819db5be2\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recent call last):\\n  File \\\"/var/task/app.py\\\", line 164, in handler\\n    process(record, degraded=degraded)\\n    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^\\n  File \\\"/var/task/app.py\\\", line 102, in process\\n    order_id = body[\\\"order_id\\\"]\\n               ~~~~^^^^^^^^^^^^\\nKeyError: 'order_id'\",\"exception_name\":\"KeyError\",\"stack_trace\":{\"type\":\"KeyError\",\"value\":\"'order_id'\",\"module\":\"builtins\",\"frames\":[{\"file\":\"/var/task/app.py\",\"line\":164,\"function\":\"handler\",\"statement\":\"process(record, degraded=degraded)\"},{\"file\":\"/var/task/app.py\",\"line\":102,\"function\":\"process\",\"statement\":\"order_id = body[\\\"order_id\\\"]\"}]},\"xray_trace_id\":\"1-6abec7e4-e764a5b526f1ac271dc475c5\"}\n"
          }
        ],
        "bytes_scanned": 24445,
        "scan_budget_left_bytes": 20149278
      }
    }
  15. Step 15+463 sfinish_investigationanswer rejected, sent back
    evidence_steps=2,10,12,14, summary=A malformed message lacking an 'order_id' key failed repeatedly during fulfillment processing with a KeyError, causing it to remain in flight and driving up the approximate age of the oldest message in the SQS queue., proposed_actions=Inspect the malformed message in the queue or DLQ to determine why it lacks an order_id., fault_category=poison_message, hypotheses=likely: poison message repeatedly failing and being redelivered while held in flight, causing queue age of oldest message to grow ruled_out: slow fulfillment consumer processing messages, causing queue age to increase ruled_out: timeout or hang in fulfillment function causing messages to remain in flight past visibility timeout, actions=["redrive_dlq"], root_cause_component=placed-orders, confidence=100
    Result (94 characters)
    {
      "error": "finish_investigation rejected",
      "problems": [
        "'actions' must be string, got list"
      ]
    }
  16. Step 16+464 sfinish_investigation
    confidence=100, hypotheses=likely: poison message repeatedly failing and being redelivered while held in flight, causing queue age of oldest message to grow ruled_out: slow fulfillment consumer processing messages, causing queue age to increase ruled_out: timeout or hang in fulfillment function causing messages to remain in flight past visibility timeout, summary=A malformed message lacking an 'order_id' key failed repeatedly during fulfillment processing with a KeyError, causing it to remain in flight and driving up the approximate age of the oldest message in the SQS queue., evidence_steps=2,10,12,14, fault_category=poison_message, proposed_actions=Inspect the malformed message in the queue or DLQ to determine why it lacks an order_id., actions=redrive_dlq, root_cause_component=placed-orders
    Result (30 characters)
    {
      "ok": "investigation closed"
    }
Postmortem, as the investigation wrote it

Postmortem: nightshift-queue-age

Investigation de6493c537f0, gemini gemini-3.5-flash-lite, started 2026-10-01T20:56:12+00:00.

Answer

Root cause componentplaced-orders
Fault categorypoison_message
Confidence100
Ended becausefinished

Summary

A malformed message lacking an 'order_id' key failed repeatedly during fulfillment processing with a KeyError, causing it to remain in flight and driving up the approximate age of the oldest message in the SQS queue.

Impact

The page: nightshift-queue-age was ALARM (since 2026-10-01T20:56:09Z). Reason recorded by CloudWatch: Threshold Crossed: 1 datapoint [345.0 (01/10/26 20:50:00)] was greater than or equal to the threshold (300.0).

Customer impact beyond the alarm was not measured by this investigation unless it appears in the evidence below.

Timeline

  • 2026-10-01T20:56:14+00:00 step 1: get_alarm
  • 2026-10-01T20:56:14+00:00 step 2: get_queue_stats
  • 2026-10-01T20:56:15+00:00 step 3: get_traces minutes=15, service=fulfillment
  • 2026-10-01T20:56:17+00:00 step 4: note_hypotheses hypotheses=possible: slow fulfillment consumer processing messages, causing queue age to i...
  • 2026-10-01T20:56:18+00:00 step 5: query_logs query=fields @timestamp, @message, duration | sort @timestamp desc | limit 20, minutes=15,...
  • 2026-10-01T20:56:18+00:00 step 6: get_function_config service=fulfillment
  • 2026-10-01T20:56:19+00:00 step 7: get_metrics minutes=15, namespace=AWS/Lambda, metric=Invocations, statistic=Sum
  • 2026-10-01T20:56:19+00:00 step 8: get_metrics statistic=Sum, namespace=AWS/Lambda, metric=Errors, minutes=15
  • 2026-10-01T20:56:19+00:00 step 9: get_metrics minutes=15, namespace=AWS/Lambda, metric=Duration, statistic=p99
  • 2026-10-01T20:56:22+00:00 step 10: query_logs minutes=15, service=fulfillment, query=fields @timestamp, @message | filter level = "ERROR...
  • 2026-10-01T20:56:22+00:00 step 11: get_metrics minutes=15, namespace=AWS/SQS, metric=ApproximateNumberOfMessagesVisible, statistic=Maximu...
  • 2026-10-01T20:56:22+00:00 step 12: get_metrics namespace=AWS/SQS, metric=ApproximateAgeOfOldestMessage, minutes=15, dimensions=QueueName=...
  • 2026-10-01T20:56:23+00:00 step 13: note_hypotheses hypotheses=likely: poison message repeatedly failing and being redelivered while held in f...
  • 2026-10-01T20:56:25+00:00 step 14: query_logs query=fields @timestamp, @message | filter level = "ERROR" | sort @timestamp desc | limit ...
  • 2026-10-01T20:56:26+00:00 step 15: finish_investigation evidence_steps=2,10,12,14, summary=A malformed message lacking an 'order_id' key failed re... (refused: 'actions' must be string, got list)
  • 2026-10-01T20:56:27+00:00 step 16: finish_investigation confidence=100, hypotheses=likely: poison message repeatedly failing and being redelivered...

Root cause evidence

  • Step 2, get_queue_stats: {"queues":{"nightshift-placed-orders":{"queue":{"name":"nightshift-placed-orders","waiting":0,"in_flight":7,"visibility_timeout_seconds":180},"dead_letter_queue":{"name":"nightshift-placed-orders-dlq","waiting":0,"in_flight":0,"visibility_t...(older result, shortened)
  • Step 10, query_logs: {"status":"Complete","rows":[{"@timestamp":"2026-10-01 20:54:49.422","@message":"{"level":"ERROR","location":"handler:170","message":"message failed","timestamp":"2026-10-01 20:54:49,422+0000","service":"fulfillment","c...(older result, shortened)
  • Step 12, get_metrics: {"metric":"AWS/SQS ApproximateAgeOfOldestMessage {'QueueName': 'nightshift-placed-orders'} Maximum per 60s","window_minutes":15,"points":[["20:41:00Z",0.0],["20:42:00Z",0.0],["20:43:00Z",0.0],["20:44:00Z",0.0],["20:45:00Z",0.0],["20:46:00Z"...(older result, shortened)
  • Step 14, query_logs: {"status":"Complete","rows":[{"@timestamp":"2026-10-01 20:54:49.422","@message":"{"level":"ERROR","location":"handler:170","message":"message failed","timestamp":"2026-10-01 20:54:49,422+0000","service":"fulfillment","c...(older result, shortened)

Proposed fix (for a human to decide; the agent changed nothing)

Allowlisted actions, each waiting for owner approval:

  • redrive_dlq

  • Inspect the malformed message in the queue or DLQ to determine why it lacks an order_id.

Hypotheses

  • likely: poison message repeatedly failing and being redelivered while held in flight, causing queue age of oldest message to grow
  • ruled_out: slow fulfillment consumer processing messages, causing queue age to increase
  • ruled_out: timeout or hang in fulfillment function causing messages to remain in flight past visibility timeout

Dropped along the way:

  • dead-letter queue filling up (waiting is 0)

What the investigation cost

  • 8 model calls, 38,169 input and 990 output tokens (39,159 total)
  • 16 steps, 15 s of wall clock
  • 822,242 bytes of logs scanned
  • 0 replies rejected by the provider for bad tool arguments