Your AI assistant forgot to ask: what happens when this process dies?
By Seaworthy · 8 October 2026
Photo by Taylor Vick on Unsplash
Your AI assistant forgot to ask: what happens when this process dies?
I asked an AI coding assistant to write me a small background job runner last week. It produced forty lines of clean, readable Python. It handled retries. It logged nicely. It even had a health check endpoint. Then I deployed it, sent a deploy rollover, and watched it silently kill three in-flight jobs. No error. No retry. Just gone.
The assistant never asked me what should happen when the process receives SIGTERM. I did not ask either. That is the problem.
The default is "just die"
Most process supervisors (systemd, Kubernetes, Docker, ECS, Fly, Render, whatever you use) do not kill your process instantly with SIGKILL. They send SIGTERM first and give you a grace period. The whole point of that grace period is for you to stop accepting new work, finish what you started, flush buffers, close connections, and exit cleanly.
If you do not handle SIGTERM, the kernel kills you when the grace period expires. Any open database transaction rolls back. Any request being served gets a connection reset. Any message you acknowledged but did not finish remains acknowledged. That last one is how you lose money in production.
AI assistants are especially prone to this pattern because they optimize for "the code works when I run it." Shutdown behavior is invisible in a local python app.py session. You have to actually deploy and rotate to see it fail.
What the bad pattern looks like
Here is a typical generated server. It looks fine. It is not.
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
# Pretend this calls a payment provider and writes to a ledger.
time.sleep(3)
process_payment(body)
self.send_response(200)
self.end_headers()
if __name__ == "__main__":
server = HTTPServer(("0.0.0.0", 8080), Handler)
server.serve_forever()
When the orchestrator sends SIGTERM, this process terminates. process_payment is midway through. The client got no response. The ledger may or may not have the row. On the next deploy you have no idea which payments landed.
A fix that respects the grace period
You do not need a framework. You need three things: a signal handler, a flag the request loop checks, and a drain window shorter than the orchestrator's grace period.
import signal, threading, time
from http.server import HTTPServer, ThreadingHTTPServer, BaseHTTPRequestHandler
shutting_down = threading.Event()
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if shutting_down.is_set():
self.send_response(503)
self.send_header("Connection", "close")
self.end_headers()
return
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
process_payment(body)
self.send_response(200)
self.end_headers()
def handle_sigterm(signum, frame):
shutting_down.set()
if __name__ == "__main__":
signal.signal(signal.SIGTERM, handle_sigterm)
server = ThreadingHTTPServer(("0.0.0.0", 8080), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
while not shutting_down.is_set():
time.sleep(0.2)
# Stop accepting new connections, then wait for in-flight requests.
server.shutdown()
server.server_close()
time.sleep(2) # Replace with a real drain check in production.
That is not beautiful. It is honest. It says: new work is refused, existing work is allowed to finish, then we exit before the orchestrator loses patience.
Why this keeps happening to AI-generated code
Assistants are trained on a huge amount of tutorial code. Tutorials almost never include signal handling because the author wants to show you the happy path in a single terminal window. So the assistant has learned that serve_forever() is the end of the story. It has not learned that the story continues after the deploy button.
I do not blame the assistant. I blame the workflow. We paste generated code, we run it locally, it works, we ship it. Nobody asks "what happens on SIGTERM" because nothing in the local loop sends SIGTERM.
A short checklist to run before you ship anything long-lived
Ask these questions out loud. If you are using an AI assistant, paste the questions into the same session and make it answer them about its own output.
- What signal does my supervisor send first, and what is the grace period?
- Where in my code do I stop accepting new work?
- How do I know when in-flight work is finished?
- What happens to messages I already acknowledged but have not completed?
- Does my drain timeout fit inside the grace period with margin?
The fifth one bites people often. Kubernetes defaults to 30 seconds. If your graceful shutdown waits 45 seconds for a slow job, you get SIGKILL anyway and all that work is wasted.
The uncomfortable opinion
Graceful shutdown is not optional polish. It is part of the contract between your process and the system that runs it. Skipping it is like writing a function that closes a file descriptor only when the program crashes. It will work in testing and it will embarrass you in production.
So next time an AI assistant hands you a server, do not accept the code as complete. Ask what it does when the operating system says stop. If the answer is "nothing," you have found the next thing to fix.
Seaworthy is a developer security tool that scans code and configuration for operational gaps like missing shutdown handling. You can find it at seaworthy.dev.
This article was generated by AI and summarises publicly available sources.
Seaworthy scans your repo for the issues covered in articles like this one.
Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.