The day Claude caught a bug I'd been ignoring for a week
A real bug-hunt session from last month. The bug had been annoying me for a week. I had a guess about the cause. The guess was wrong. The agent found the right cause in 15 minutes.
The setup: a Python script that watches a directory for new files and uploads them to S3. The script had been running fine for months. Last Tuesday it stopped uploading. The script was still running. The log file was being written. The S3 bucket existed. The IAM role hadn’t changed. Nothing obvious.
My wrong guess
I assumed the S3 credentials had rotated. The script uses IAM role assumption, and those roles can be flaky. I checked the role. The role was fine. I checked the script’s STS client. The STS client was fine. I checked the S3 endpoint. The endpoint was fine. I was three days into a wild goose chase.
The session
I opened a fresh Claude session, gave it the relevant files (uploader.py, the IAM config, the log file), and asked:
The script in
uploader.pystopped uploading to S3 on Tuesday. The log file is inuploader.log. The IAM role is iniam.json. Find the bug.
The agent’s first response was a list of five things to check. The list was reasonable. I told it which ones I’d already checked (the IAM role, the S3 endpoint, the bucket policy). I told it to skip those and look at the rest.
The agent’s second response was a more focused list. The first item was “check the file mtime comparison in the polling loop.” I had been assuming the bug was in the upload path. I hadn’t been looking at the polling path.
The actual bug
The script was comparing the file’s mtime to the current time to decide whether to upload. The comparison used os.path.getmtime() which returns seconds since epoch. The script compared that to time.time() which also returns seconds since epoch. The math was right.
The bug was in the threshold. The script was supposed to upload files that had been stable for 60 seconds (to avoid uploading files that were still being written). The threshold was hardcoded to 60. The script was being run on a system where the system clock was being set by NTP, and the NTP correction at 03:00 that Tuesday had jumped the system clock backward by 0.7 seconds. The threshold check saw “file mtime is 0.7 seconds in the future” and rejected every file as “not yet stable.”
The bug was a 0.7-second clock jump that put the file mtime slightly ahead of the system time. The 60-second threshold check failed. The script was waiting forever for files to “stabilize” (they were already 0.7 seconds old but the script thought they were 0.7 seconds in the future). The fix was to use a tolerance in the comparison, or to use a monotonic clock.
I had spent a week assuming the bug was in the network. The bug was in the time math. The agent found it by reading the code, not by checking the network.
The prompts that worked
The first prompt (“find the bug”) was too broad. The agent returned a checklist. The checklist was useful but I had already done the network checks.
The second prompt (“I checked the network. Look at the rest.”) was the right one. It narrowed the search space. The agent could focus on the code path I hadn’t checked.
The third prompt (“explain why this would only manifest on Tuesday”) was the killer prompt. It made the agent reason about the timing, which led to the clock jump. The clock jump is the kind of thing you only find if you ask “why Tuesday” instead of “why broken.”
The prompt that didn’t work
I tried one prompt that didn’t work:
Use git blame to find when the threshold was last changed and check if anything in the system changed that day.
The agent tried. The git log didn’t show anything suspicious. The agent reported back “no changes found in git that would explain the bug.” That was correct but unhelpful. The bug wasn’t in the code. The bug was in the runtime environment. The prompt assumed the bug was in the code.
The lesson
The lesson isn’t “use Claude to find bugs.” The lesson is “frame the prompt right.” A specific, scoped question gets a specific, useful answer. A broad question gets a checklist. The art of using these tools is asking the question that points the model at the right slice of the problem.
For this bug, the right slice was “why did this start failing on Tuesday, given that nothing changed in the code or the network.” The model pointed at the system clock. I had been assuming the code was the problem because the code was the only thing I’d been reading.
The agent reads everything. The agent is good at that. The agent is bad at knowing which part of everything is the problem. The prompt tells the agent which part.