---
title: "Why item_close.py Never Reached the Code That Set the Sprint"
canonical: https://dxdev.com/blog/the-automation-that-never-ran/
datePublished: 2026-08-14
---
The screenshot came with one line under it: tickets keep skipping the current sprint. Also the developer field is empty. Also, if there's code involved, the fix version isn't there either. Not once. Every time.

That kind of report used to send us spelunking through the ticket tracker's UI, trying to remember which screen has which field. This time we had an agent go look at the actual close tooling instead, the script that's supposed to set all three of those fields automatically when a ticket gets closed.

## The code that said it already handled this

The close path runs through `item_close.py`, a large script that walks a ticket through the screen-bearing close transition, the one that accepts Story Points, Sprint, and Fix Versions in the same POST. The comments say, in plain language, that the values are auto-stamped from the next-upcoming release and the active sprint "when missing, so they no longer cause halts."

The code isn't ambiguous about what it's supposed to do. Someone had written logic for exactly the problem we were staring at. Parse the sprint field, which keeps the full history of every sprint a ticket ever rolled through. Skip the closed ones. Find the one still marked active. Stamp it. Same idea for fix version. It was designed, written, and merged.

And it had never once fired. Not on a real ticket, not for us, not for anyone.

## Tracing the call path instead of the comments

The script appeared to work in whatever fixture or dry run it was demoed against, and then it never ran again. Nothing downstream of "merged" was watching whether it executed on real tickets.

Once the agent traced the actual call path instead of reading the comments describing it, the picture got worse. The auto-stamp logic sat behind a chain of preconditions. Get the transition list. Confirm the close transition is available from the ticket's current status. Confirm story points are already set, because the code treats that as a hard requirement and dies if it's missing. Only after all of that does it reach the block that resolves sprint and fix version.

Five separate points in that chain could silently take the whole thing down, and they stacked. The first was the dumbest one: an import at the top of the file that crashed the CLI before a single line of the close logic ran. The script never got past its own header. Everything after it, the sprint parsing, the active-sprint preference, the fix version resolution, the story points check, was correct code that had never been executed, because execution never got that far.

## Careful code, and nobody watching it

Nobody wrote sloppy code here. When we finally read the sprint-resolution logic in isolation, it was careful. It knew the difference between a ticket's full sprint history and its current active sprint. It knew fix version and sprint needed to be resolved at plan-preview time but written only through the transition itself, so a dry run stayed read-only.

What nobody built was a way to tell "this logic exists in the file" from "this logic ran on recent closes." The demo passed. The fixture passed. Then it shipped, and from that point the only signal anyone had that it was working was the absence of complaints, which is silence.

All three broken fields, sprint, developer, fix version, turned up in the same screenshot on the same day, because we happened to be looking. If we hadn't looked, the auto-stamp logic would still be sitting in that file today, present, correct past its first line, and doing nothing.

## What we're changing

The fix for the import took five minutes. The fix for our process is the part we care about. A feature that closes tickets and touches ticket fields needs a live check, not just a merged PR and a demo screenshot. Something has to confirm, on an actual close, that the fields it claims to stamp got stamped.

Shipped was never the bar. Running was.
