---
title: "The workflow broke in the glue code, not the AI"
canonical: https://dxdev.com/blog/2026-04-27_workflow-broke-in-glue-code/
datePublished: 2026-04-27
---
On the morning of April 27, the session hook that routes my Claude Code work was dropping the current working directory on every third invocation. I had noticed sessions starting with stale ticket context a handful of times over the previous three weeks, cleared the window, restarted, and moved on. That fixed the symptom and masked the cause each time.

When I finally traced it, the bug was three lines in a thirteen-line shell wrapper. `$PWD` was resolving to the caller's cwd rather than the project root whenever the hook fired from a process that had changed directories since startup. A missing `cd` produced three weeks of intermittent session drift, manual corrections, and half-wrong context.

## the gap was in a thirteen-line shell wrapper

I had opened ITEM-6867 as a grab-bag for exactly this kind of problem: small process gaps that cost attention without producing a visible incident. The CWD bug was item one. The grab-bag had four more.

The `collaborators.md` file gives Claude a working list of who touches which parts of the codebase. Mine was two months out of date and still listed a developer who had left in February. For two months, Claude had been deferring to that person in review comments. Attributing code to them, suggesting I CC them on findings, flagging their name in context summaries. Technically competent. None of it accurate.

The `organize-stage` script writes story points and `fixVersion` back to JIRA after a ticket closes. Half my tickets were on an older field schema using `customfield_10028`. The script had been writing to `customfield_10016` since an epic migration I did in January. The writes appeared to succeed. Nothing errored. The values just went nowhere. I had been manually fixing the output for three weeks without asking why it kept being wrong. I thought I was doing quality control. I was compensating for a broken tool.

The scope-creep check fires when Claude attempts a fix outside the ticket's stated scope. The check itself worked. The rejection comment it wrote to JIRA was getting silently truncated at 255 characters by the API. The comment looked complete in the write response. To anyone reading the ticket, it read like an incomplete thought, missing whatever came after character 255.

The close-comment generator was producing 400-word technical summaries. That field in JIRA is read on a phone, usually by someone who wants to know if the ticket is done. The close flow had been optimized for comprehensiveness over readability, and I had been trimming the output by hand before posting. Sometimes I trimmed wrong.

None of these was an emergency. Each one was a small lie the workflow told about itself. The compound cost of believing all of them across a week of sessions was real in a way that a single blocked day would have been more obviously real.

## what the fixes actually looked like

The CWD fix was three lines. I pinned the hook to `realpath "$(dirname "${BASH_SOURCE[0]}")"` on startup and passed it down explicitly rather than relying on `$PWD`.

`collaborators.md` was fifteen minutes. One departure removed, one addition for a contractor who had been contributing for six weeks. The file now has a `last-updated` line at the top, so next time I forget, I will at least know how long I forgot for.

The `organize-stage` schema divergence took an hour to diagnose and fifteen minutes to fix. The two field IDs coexist in my JIRA instance because I migrated epics but not closed tickets onto the new scheme. The fix was a lookup table keyed by issuetype. Epic and Feature get `customfield_10016`, everything else gets `customfield_10028`. I verified it against six tickets before shipping.

The JIRA character limit needed more restructuring. The rejection comment was formatted as a preamble plus explanation plus ticket reference. The preamble alone was 180 characters. I shortened it to 40 and the full comment now fits in 255 with room. Anything that genuinely overflows goes to the Dev Notes field instead, which has no size constraint.

The close comment got capped at four sentences. I tested it against eight recent closes. Four sentences is enough. The technical detail goes into the JIRA Dev Notes field on close, not the comment. The comment is for whoever glances at the ticket tomorrow. The Dev Notes are for whoever debugs something six months from now.

## closing the ticket exposed a missing seam

The last gap surfaced while I was writing the close comment for ITEM-6867 itself. My close flow reads the ticket's resolution, generates a summary, and posts it to JIRA. The flow assumed the ticket was in an open state. ITEM-6867 was already in Done. The close flow failed on a transition guard and produced no comment.

The fix was a closed-ticket append path. Detect Done state, skip the transition step, post the comment directly via the comment API. Twenty additional lines. The close flow now handles tickets in any state.

I almost did not write this down. It felt like infrastructure noise beside the actual deliverables. That instinct is exactly how this category of bug accumulates. Closing tickets is a frequent operation. A close flow that breaks on already-closed tickets had been silently failing every time I manually moved a ticket to Done before running the close flow. I don't know how many close comments went missing before this one.

## the maintenance that doesn't look like maintenance

An internal reference doc the agent reads for JIRA field values and transitions got an Issue Types section that day. That document is what Claude reads to understand valid field values, legal transitions, and which workflow paths apply to which ticket types. It had never had a clean Issue Types listing with their numeric IDs. I had been letting the agent infer the types from context, which works most of the time and fails in ways that are hard to reproduce when it doesn't.

Writing the section took twenty minutes. While I was in the file, I flagged `hto_dev` as unused. That workflow type was scaffolded in early 2024 for a developer-facing ticket category that never went into production. It had been sitting in the transition table for about two years, reachable from the agent, prompting occasional confused transition attempts on tickets that had no business going there. A single comment: `# DEPRECATED. Not in use as of 2024-03, do not route here`.

Neither of these changes feels like a deliverable. But the agent reads that reference doc on every JIRA operation. A missing Issue Types section is an invitation to guess. The agent was guessing wrong roughly once a week, and I was correcting it by hand, not registering that I was doing so.

## what the pattern is actually saying

The expensive failures in AI-assisted development are not usually model failures. The model degrades gracefully. It hedges, it asks, it recovers when corrected. Glue code fails silently. A 255-character JIRA truncation produces a comment that looks complete in the API response and reads like an incomplete thought to the human reviewer. A wrong field ID produces an apparently-successful write that goes nowhere. A stale `collaborators.md` produces confident but wrong attribution, indistinguishable from correct attribution unless you already know who left in February.

The model is not the first place to look. The session hook, the field mappings, the reference docs the agent reads to understand the JIRA schema, the status transitions the close flow assumes, the collaborators list that drifts as people come and go. These are where the workflow lies to itself without flagging an error.

Auditing this layer doesn't feel like progress. A three-line shell fix is not a feature. It doesn't appear in a changelog users will ever read. But the features ship into the workflow, not around it. If the workflow is lying about which directory it's in, you are not getting the full value of anything built on top.

The first thing to harden in an AI coding loop is the thirteen-line wrapper that tells the model where it is. Not the model.
