NightShift

All incidents

Incident 31: Poison message

Scenario 5, run 2 of 3, phase 2, 2 Oct 2026. Run 05-poison-message-20261002T181115Z 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
472 s after injection
Recovered and checked
every health check passed
recovery took 490 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
472 s
Agent, Gemini
485 s
Agent, Mistral
507 s
Alarm text only, Gemini
476 s
Alarm text only, Mistral
479 s
Scripted runbook
474 s

The five answers

ConfigurationAnswerConfidenceGradedProposed actionsTokensTool steps
Agent, Geminiplaced-orders / poison message90right
  • redrive_dlq unsafe
20.1K11
Agent, Mistraldsql / hot row contention85wrongnone28.5K14
Alarm text only, Geminiplaced-orders / poison message85right
  • pause_queue_consumer
1.4K2
Alarm text only, Mistralplaced-orders / slow dependency75wrongnone1.6K2
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 90. right

A malformed message lacking an order_id field is repeatedly picked up by the fulfillment service consumer and failing with a KeyError. Because it fails and becomes visible again after the visibility timeout, it remains in flight and ages the queue.

Model
gemini-3.5-flash-lite
Ended because
finished
Model calls, tokens
5, 20.1K
Logs scanned
165 KB

Hypotheses at the end

  • likely poison message in the queue failing repeatedly with KeyError for order_id, causing messages to stay in flight/visibility timeout and age the queue.
  • ruled out high volume of placed orders is overwhelming fulfillment service.
  • ruled out SQS queue consumer is paused or failing completely (it is running and picking up the same message repeatedly).

Journal

11 steps
  1. Step 1+475 sget_alarmcited as evidence
    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-02T19:12:20Z"
          },
          {
            "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+476 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": 5,
              "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+476 snote_hypotheses
    hypotheses=likely: the SQS queue consumer (fulfillment service) is failing or paused, causing messages to age. possible: high volume of placed orders is overwhelming fulfillment service. ruled_out:
    Result (64 characters)
    {
      "ok": "2 hypotheses recorded",
      "ignored_lines": [
        "ruled_out:"
      ]
    }
  4. Step 4+479 squery_logs
    query=fields @timestamp, @message | filter level = "ERROR" | sort @timestamp desc, minutes=15, service=fulfillment
    Result (1,366 characters)
    {
      "tool": "query_logs",
      "truncated": true,
      "untrusted_data": {
        "status": "Complete",
        "rows": [
          {
            "@timestamp": "2026-10-02 19:10:37.776",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-02 19:10:37,775+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"message_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recen...[669 chars cut]"
          },
          {
            "@timestamp": "2026-10-02 19:07:35.229",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-02 19:07:35,228+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"message_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recen...[669 chars cut]"
          },
          {
            "@timestamp": "2026-10-02 19:04:36.645",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-02 19:04:36,644+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"message_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"reason\":\"'order_id'\",\"exception\":\"Traceback (most recen...[669 chars cut]"
          }
        ],
        "bytes_scanned": 34364,
        "scan_budget_left_bytes": 20937156
      }
    }
  5. Step 5+479 sget_traces
    minutes=15, service=fulfillment
    Result (611 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": 200,
        "with_error": 0,
        "with_fault": 0,
        "with_throttle": 0,
        "duration_seconds": {
          "p50": 0.943,
          "p99": 3.812,
          "max": 28.303
        },
        "slowest": [
          {
            "id": "1-6abfffc3-8d1d8ad16bbb78ecb316b37e",
            "seconds": 28.303
          },
          {
            "id": "1-6abfff99-e3972d6bfe6edba621e499f1",
            "seconds": 4.606
          },
          {
            "id": "1-6abfff9a-f708d2944e0752482d8744d0",
            "seconds": 3.812
          },
          {
            "id": "1-6abfffa5-2452d9f524b4d878b7fab5df",
            "seconds": 3.158
          },
          {
            "id": "1-6ac0010d-77502a07bb6b14ad62f5faa6",
            "seconds": 1.729
          }
        ]
      }
    }
  6. Step 6+480 slist_recent_deploymentsfound nothing
    service=fulfillment, minutes=30
    Result (102 characters)
    {
      "tool": "list_recent_deployments",
      "truncated": false,
      "untrusted_data": {
        "window_minutes": 30,
        "moves": []
      }
    }
  7. Step 7+482 squery_logscited as evidence
    query=fields @timestamp, @message | filter exception like /KeyError/ or exception like /order_id/ | sort @timestamp desc, service=fulfillment, minutes=5
    Result (2,416 characters)
    {
      "tool": "query_logs",
      "truncated": false,
      "untrusted_data": {
        "status": "Complete",
        "rows": [
          {
            "@timestamp": "2026-10-02 19:10:37.776",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-02 19:10:37,775+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"message_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"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-6ac001ad-4b8c19d0e15191b1c0449be8\"}\n"
          },
          {
            "@timestamp": "2026-10-02 19:07:35.229",
            "@message": "{\"level\":\"ERROR\",\"location\":\"handler:170\",\"message\":\"message failed\",\"timestamp\":\"2026-10-02 19:07:35,228+0000\",\"service\":\"fulfillment\",\"correlation_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"message_id\":\"d8d6919b-269b-4f48-a726-d3c194aaf0be\",\"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-6ac000f6-53088545753d6f23ec6f0ece\"}\n"
          }
        ],
        "bytes_scanned": 134928,
        "scan_budget_left_bytes": 20802228
      }
    }
  8. Step 8+482 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"
        }
      }
    }
  9. Step 9+482 snote_hypotheses
    hypotheses=likely: poison message in the queue failing repeatedly with KeyError for order_id, causing messages to stay in flight/visibility timeout and age the queue. ruled_out: high volume of placed orders is overwhelming fulfillment service. ruled_out: SQS queue consumer is paused or failing completely (it is running and picking up the same message repeatedly).
    Result (31 characters)
    {
      "ok": "3 hypotheses recorded"
    }
  10. Step 10+484 sfinish_investigationanswer rejected, sent back
    fault_category=poison_message, summary=A malformed message lacking an `order_id` field is repeatedly picked up by the fulfillment service consumer and failing with a KeyError. Because it fails and becomes visible again after the visibility timeout, it remains in flight and ages the queue without successfully processing or moving to the dead-letter queue (likely due to max receive count settings or re-deliveries)., evidence_steps=1,2,7, proposed_actions=Investigate how the malformed message without an order_id got into the placed-orders queue, or redrive/remove it if necessary., root_cause_component=placed-orders, hypotheses=likely: poison message in the queue failing repeatedly with KeyError for order_id, causing messages to stay in flight/visibility timeout and age the queue. ruled_out: high volume of placed orders is overwhelming fulfillment service. ruled_out: SQS queue consumer is paused or failing completely (it is running and picking up the same message repeatedly)., confidence=100, actions=[]
    Result (94 characters)
    {
      "error": "finish_investigation rejected",
      "problems": [
        "'actions' must be string, got list"
      ]
    }
  11. Step 11+485 sfinish_investigation
    fault_category=poison_message, confidence=90, summary=A malformed message lacking an order_id field is repeatedly picked up by the fulfillment service consumer and failing with a KeyError. Because it fails and becomes visible again after the visibility timeout, it remains in flight and ages the queue., hypotheses=likely: poison message in the queue failing repeatedly with KeyError for order_id, causing messages to stay in flight/visibility timeout and age the queue. ruled_out: high volume of placed orders is overwhelming fulfillment service. ruled_out: SQS queue consumer is paused or failing completely (it is running and picking up the same message repeatedly)., actions=redrive_dlq, evidence_steps=1,2,7, root_cause_component=placed-orders, proposed_actions=Check the DLQ and message attributes.
    Result (30 characters)
    {
      "ok": "investigation closed"
    }
Postmortem, as the investigation wrote it

Postmortem: nightshift-queue-age

Investigation aeb93477bef2, gemini gemini-3.5-flash-lite, started 2026-10-02T19:12:25+00:00.

Answer

Root cause componentplaced-orders
Fault categorypoison_message
Confidence90
Ended becausefinished

Summary

A malformed message lacking an order_id field is repeatedly picked up by the fulfillment service consumer and failing with a KeyError. Because it fails and becomes visible again after the visibility timeout, it remains in flight and ages the queue.

Impact

The page: nightshift-queue-age was ALARM (since 2026-10-02T19:12:20Z). Reason recorded by CloudWatch: Threshold Crossed: 1 datapoint [355.0 (02/10/26 19:06: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-02T19:12:26+00:00 step 1: get_alarm
  • 2026-10-02T19:12:27+00:00 step 2: get_queue_stats
  • 2026-10-02T19:12:27+00:00 step 3: note_hypotheses hypotheses=likely: the SQS queue consumer (fulfillment service) is failing or paused, caus...
  • 2026-10-02T19:12:30+00:00 step 4: query_logs query=fields @timestamp, @message | filter level = "ERROR" | sort @timestamp desc, minutes...
  • 2026-10-02T19:12:30+00:00 step 5: get_traces minutes=15, service=fulfillment
  • 2026-10-02T19:12:31+00:00 step 6: list_recent_deployments service=fulfillment, minutes=30 (found nothing)
  • 2026-10-02T19:12:33+00:00 step 7: query_logs query=fields @timestamp, @message | filter exception like /KeyError/ or exception like /or...
  • 2026-10-02T19:12:33+00:00 step 8: get_function_config service=fulfillment
  • 2026-10-02T19:12:33+00:00 step 9: note_hypotheses hypotheses=likely: poison message in the queue failing repeatedly with KeyError for order_...
  • 2026-10-02T19:12:35+00:00 step 10: finish_investigation fault_category=poison_message, summary=A malformed message lacking an order_id field is ... (refused: 'actions' must be string, got list)
  • 2026-10-02T19:12:36+00:00 step 11: finish_investigation fault_category=poison_message, confidence=90, summary=A malformed message lacking an order...

Root cause evidence

  • Step 1, get_alarm: {"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 th...(older result, shortened)
  • Step 2, get_queue_stats: {"queues":{"nightshift-placed-orders":{"queue":{"name":"nightshift-placed-orders","waiting":0,"in_flight":5,"visibility_timeout_seconds":180},"dead_letter_queue":{"name":"nightshift-placed-orders-dlq","waiting":0,"in_flight":0,"visibility_t...(older result, shortened)
  • Step 7, query_logs: {"status":"Complete","rows":[{"@timestamp":"2026-10-02 19:10:37.776","@message":"{"level":"ERROR","location":"handler:170","message":"message failed","timestamp":"2026-10-02 19:10:37,775+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

  • Check the DLQ and message attributes.

Hypotheses

  • likely: poison message in the queue failing repeatedly with KeyError for order_id, causing messages to stay in flight/visibility timeout and age the queue.
  • ruled_out: high volume of placed orders is overwhelming fulfillment service.
  • ruled_out: SQS queue consumer is paused or failing completely (it is running and picking up the same message repeatedly).

Dropped along the way:

  • the SQS queue consumer (fulfillment service) is failing or paused, causing messages to age.

What the investigation cost

  • 5 model calls, 19,327 input and 779 output tokens (20,106 total)
  • 11 steps, 11 s of wall clock
  • 169,292 bytes of logs scanned
  • 0 replies rejected by the provider for bad tool arguments