---
title: "The Nightly Job That Filed TLS Cert Renewal as Internal Work"
canonical: https://dxdev.com/blog/classified-by-where-the-code-landed/
datePublished: 2026-07-26
---
Five rows had moved off the product board and onto the internal one. We caught it only because we happened to open the internal board first that morning.

The classifier is a nightly job. It pulls everything touched in the last cycle, looks at where the commits landed, and files each ticket into either the product backlog or the internal ops backlog. Five tickets that week were customer-facing work, TLS cert renewal, a visibility bug on a customer's event page, downgrade billing logic, and all of them had been filed as internal. We reversed three within minutes of looking. The other two took longer, because the shape of the mistake wasn't obvious yet.

## What the rule actually said

The logic was repo-based. If the commit shipped from our internal tooling repo, tag it internal. If it shipped from the product API repo or the main product repo, tag it product. That is a reasonable first pass, and it had run clean for weeks because most internal-repo commits really were internal work: agent hooks, guardrails, dashboards.

Then the ticket for the failing event images came through. Customer sites were failing to load images. The root cause was an expired TLS cert on the media origin server, and the fix was reinstalling a replacement cert that had been issued back in May and never deployed. That is customer-facing breakage with a same-day fix. But the commit that closed it lived in our internal tooling repo, because that is where our infra scripts live, so the classifier filed it as internal tooling maintenance.

The downgrade/auto-renew billing fix went the same way. A customer was being charged a full year early, and the ticket landed next to a lint-error cleanup because the patch touched an internal billing helper.

## The wrong turn

Our first fix was to patch the rows. We went into the ticket tracker, manually re-filed the three tickets we'd caught, and told ourselves we would spot-check the nightly output more carefully from then on.

That lasted about four days. The next batch let two more tickets through the same way. One of them was the event visibility bug, which is unambiguously a customer-facing defect, but it shipped as a one-line config read inside our internal tooling repo because that is where the diagnostic tooling lived.

Re-filing rows corrects the output and leaves the machine that produced it unchanged, so the same misclassification regenerated itself on the same schedule. We had spent two review cycles doing manual triage that the classifier should have done itself, and the backlog was no more trustworthy at the end of the second cycle than the first.

## Keying on the delivery target

The rule needed to answer "who does this deliver to." So we rewrote the classifier to look at the ticket's own delivery target instead of the commit's file path. The question became whether the fix changes behavior a customer or their site visitors experience, regardless of which repo housed the code that changed it.

Concretely, the classifier now checks the ticket description and any linked customer or site references first. It falls back to repo path only when no delivery target is named at all, which in practice is true for internal work like the Claude settings hook migration or the CI lint fix.

We ran it back against the prior week's 71 sessions and 27 commits to check for regressions. The infra-only tickets (hook migration, CI debugging, prod reboot triage) stayed internal. The five customer-facing ones, cert renewal, billing fix, event visibility and the rest, classified correctly on the first pass, with no manual re-filing.

## A rewrite, twice in one week

This was the second time that week a review turned into a rewrite of the rule instead of a fix to its output. The classifier had also been running unattended for hours at a stretch, and the log shows the same untitled session firing every half hour overnight. That cadence makes it easy to treat a misfile as a one-off glitch to nudge back into place. But an unattended job that is wrong in a structural way produces the same wrong answer at the same time tomorrow.

What catching it by hand bought us was not the three rows we moved back. It was noticing that the classification question itself was wrong, and that only shows up if you read what the tickets say rather than where the diff landed.

If a nightly job files work by where the code landed, check what it is actually keyed on. Correcting its output rows did not stop the same misfile from recurring within about four days.
