The Health Check Said 200. ss Showed a Different Interpreter.
Have you ever celebrated a green health check and then noticed nobody could actually use the app? I spent a messy forty-eight hours on that exact feeling, and the logs never once contradicted me. The checker kept hitting something that answered politely, so I kept blaming the client instead. The process I actually cared about was never the one holding that soothing open port. This is not a manifesto about microservices, meshes, or the correct way to ship health. It is a set of field notes on a tautological smoke test, plus a receipt I wish I had run on hour one. If you generate servers with a coding model and then run them on a laptop and a remote box, this trap is waiting. Why did a 200 feel like a lie? I asked a free coding model for the smallest possible smoke test around a tiny HTTP app. The draft looked responsible: start the server, sleep one beat, request the path, assert 200, and print OK. What could possibly go wrong with four lines that every tutorial keeps repeating without shame? The first problem was not the framework, the router, or some fashionable async runtime hiding under the floor. The problem was the word localhost, which feels like a constant until you actually change machines. My laptop still had an old listener on the same port from a previous experiment I had forgotten to kill. The generated checker never asked who answered the socket. It only asked whether somebody, anybody, answered it. Have you looked at ss on the same heartbeat as your client, or do you trust the status line? I trusted the status line, because 200 is a very soothing number when you are tired. Soothing numbers are how you donate a whole day to the wrong process. What I tried while the port still answered I did the usual local dance first, because that is what tired people do when a green check appears. Restart the app and rerun the checker until both look green in the same terminal scrollback. Print the port in the server logs and in the checker logs, then declare the numbers matched. Switch from curl to urllib so the test supposedly belongs to Python instead of a shell. Blame the remote environment when the same script felt flaky after I copied the files over. None of those steps asked a process identity question, which is the only question that mattered. They asked a port question, and ports are shared, recycled, and inherited by whatever started first. Python's stdlib HTTPServer also sets address reuse, so a successful bind is not exclusive ownership of that tuple. Here is the shape of the generated checker, reconstructed as a lab example rather than a transcript of a private chat: # lab example — not a recorded transcript import urllib.request url = "http://localhost:8000/health" with urllib.request.urlopen(url, timeout=2) as response: assert response.status == 200 print("ok") Looks harmless, right? It is harmless until two interpreters exist on the same port story. Then it becomes a coin flip with extra logging and a very confident exit code. The break: two listeners, one hostname The break showed up when I finally printed sys.executable and os.getpid() from the app, and then from a response header the app controlled. The checker was still happy, because happiness was defined as status 200. The header was not the nonce I had just started in this shell. I had been scoring a previous process that never died, and it was still doing customer-service impressions. On the laptop, localhost meant my leftover listener, which had survived a terminal I thought I had closed. On the remote box, localhost meant a different network namespace than the one I was calling from my own machine. Two failures, one slogan, and the slogan was "the service is up." Would you have killed the old process first, or would you have trusted the client too? I used MonkeyCode for the pairing that finally made the contradiction visible: free model access to draft the checker, and a free server option so the laptop could not keep answering the socket. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I am not claiming a particular model name, a quota, a GPU SKU, or a forever-free promise. I am claiming this workflow only: generate on one side, then execute on a machine that is not your leftover port. A receipt you can rerun The artifact is a pair of stdlib scripts you can read in one sitting. One server stamps every response with a nonce you choose at boot. One checker refuses to accept 200 unless that nonce, the path, and the explicit host all match. Label: this is a lab receipt, not a production health system, and not a claim that I published a benchmark. The server that names itself # nonce_server.py — lab example from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import os import sys NONCE = os.environ["APP_NONCE"] HOST = os.environ.get("BIND_HOST", "127.0.0.1") PORT = int(os.environ.get("BIND_PORT", "8000")) class Handler(BaseHTTPRequestHandler): def do_GET(self): if self.path != "/health": self.send_error(404) return body = b"ok\n" self.send_response(200) self.send_header("Content-Type", "text/plain") self.send_header("X-App-Nonce", NONCE) self.send_header("X-App-Pid", str(os.getpid())) self.send_header("X-App-Executable", sys.executable) self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def log_message(self, fmt, *args): sys.stderr.write("server pid=%s exe=%s " % (os.getpid(), sys.executable)) sys.stderr.write((fmt % args) + "\n") if __name__ == "__main__": httpd = ThreadingHTTPServer((HOST, PORT), Handler) print( "listening", httpd.server_address, "pid", os.getpid(), "exe", sys.executable, "nonce", NONCE, flush=True, ) httpd.serve_forever() The checker that can fail on purpose # smoke_check.py — lab example import argparse import sys import urllib.error import urllib.request def main(): parser = argparse.ArgumentParser() parser.add_argument("--host", required=True) parser.add_argument("--port", type=int, required=True) parser.add_argument("--expect-nonce", required=True) parser.add_argument("--path", default="/health") args = parser.parse_args() url = "http://%s:%s%s" % (args.host, args.port, args.path) try: with urllib.request.urlopen(url, timeout=2) as response: nonce = response.headers.get("X-App-Nonce", "") pid = response.headers.get("X-App-Pid", "") exe = response.headers.get("X-App-Executable", "") status = response.status body = response.read(64) except urllib.error.URLError as exc: print("UNREACHABLE", url, exc) return 2 print("status", status, "nonce", nonce, "pid", pid, "exe", exe, "body", body) if status != 200: print("BAD_STATUS") return 3 if nonce != args.expect_nonce: print("WRONG_PROCESS expected", args.expect_nonce) return 4 print("MATCH") return 0 if __name__ == "__main__": sys.exit(main()) Commands I would type in order export APP_NONCE="run-$(date +%s)" export BIND_HOST=127.0.0.1 export BIND_PORT=8000 python3 nonce_server.py # other terminal, same machine first python3 smoke_check.py --host 127.0.0.1 --port 8000 --expect-nonce "$APP_NONCE" Then copy the same two files to the remote box and export a fresh nonce there. Do not reuse the laptop nonce, because reuse turns the header back into theater. Do not let the checker default the host, even if a model offers a shorter command. If the remote checker needs 127.0.0.1, say that out loud in the argv. If you are calling from your laptop toward the remote process, you need the remote bind address that is actually reachable, not the word localhost from a model reply. A second snapshot I now take before I believe anyone, including myself: python3 -
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to