The wrapper handed my agent a schema sketch instead of the payload, then hid the merge commit at HEAD while I was closing the ticket from another machine. I had assumed the token-saving filter was a safe default for every command, and that assumption was wrong because the status check and merge check depended on literal fields and a merge node it could remove.

I run a CLI wrapper that compresses verbose command output before it reaches my AI agent. It groups thousands of lines of test failures into a summary, collapses a noisy git diff into the parts that changed, turns a wall of JSON into a shape. On a Max plan that I was burning through running agents all day, that compression is not a nicety. It is 60 to 90 percent off the token bill on the commands I run constantly: git, curl, gh, test runners. It saves real money.

It also, on the one afternoon I most needed authoritative output, handed my agent a sketch of my data instead of the data, and erased the exact commit I was trying to confirm had landed.

The setup: closing a ticket from the wrong machine

I run a legacy SaaS on Windows, IIS, and SQL Server, plus a growing pile of agent tooling on my desktop. That afternoon I was trying something I had never done: close a ticket in the product repo without ever opening a session inside that repo. The code lived in one clone; my agent was rooted in a different station entirely, reaching across to drive the work over the JIRA REST API and the git CLI from the outside.

The close itself is mechanical. Merge the feature branch into the integration branch, push, clean up the local branch, fire the JIRA transition that moves the ticket to its shipped state, verify in a browser. Nothing exotic. The kind of thing you do half-asleep when the code lives under your cursor.

Reaching across machines, though, every assumption got tested. Most of the frictions that day were boring and Windows-shaped. No /tmp directory, so a hardcoded temp path blew up. Quoting hell trying to run Python inside a bash heredoc inside a Windows shell. A git branch -d that refused to delete because the local branch carried a merge commit the remote did not have yet. Papercuts.

Two of them were not papercuts. Two of them were the tool I trusted telling my agent something false.

Trap one: a schema sketch where the payload should be

Part of the close is verifying the JIRA transition actually took. So the agent pulled the issue back over the REST API to read the field values and confirm the new state. The fetch went through the wrapper, the same way every other command did.

What came back looked like this:

{ expand: string[82], fields: { ... } }

That is not the issue. That is a description of the issue. The wrapper recognized the response as a big JIRA REST payload, decided a human staring at it would not want 82 expand options and a hundred nested field objects dumped to the terminal, and helpfully replaced the contents with their types and counts. For a person skimming, that is a kindness. For an agent that needs to read the literal value of a status field to confirm a transition, it is a brick wall. The data the agent came to read was not in the output. Only the shape of it was.

The agent did the honest thing and reported it could not verify the field values. Which is correct behavior, and also exactly the wrong outcome, because the transition had in fact succeeded. The verification step failed not because the work failed, but because the tool in the middle had eaten the evidence.

Trap two: the merge commit that wasn’t there

Then I went to confirm the merge landed on HEAD. Standard move: rtk git log -1 --oneline, read the top commit, check it is the merge.

It came back a non-merge commit. According to the output, the merge had never landed.

It had. I confirmed it a minute later with git rev-parse HEAD and a raw git log with no wrapper in the path. The merge node was right there. But the compactor, trying to give me a clean one-line history, had collapsed or suppressed the merge node specifically, and surfaced a different commit as the tip. The one piece of git state I was actively trying to verify was the one piece the filter decided to tidy away.

A merge commit is the single most load-bearing object in a release. It is the thing you check before you tell anyone “it shipped.” And the tool optimized it out of view in the name of readability.

Why this was guaranteed, not unlucky

It would be comforting to call this a bug. It is the tool working exactly as designed, and that is the uncomfortable part.

The wrapper is structural. It does not blindly truncate text. It recognizes what kind of output it is looking at, a git log, a REST response, a test report, and applies a format-aware transform to make it pleasant for a human to read. That intelligence is the whole value. But every one of those transforms is built around a single assumption: a human is going to read this.

Agents do not read. Agents parse. When a human reads { fields: { ... } }, they mentally fill in “and the actual fields are in there somewhere, I will expand if I care.” When an agent parses that same string, the fields are simply gone. There is no “somewhere.” The token that represented the value was deleted and replaced with a token that represents its type, and the agent has no way to recover the original from the summary. Lossy-but-pretty is fine for eyes. For a machine doing verification, lossy is just wrong.

The two failures were the same failure wearing different clothes. Reduce-for-readability collapsed a payload into its schema, and collapsed a history into a cleaner-but-false tip. Both times the tool optimized for the reader and silently sacrificed the exact bytes the agent needed.

The fix, and the rule it forced

The fixes were small and immediate. For the API call, stop shelling out to curl entirely and make the request from inside Python with urllib. No external command means no wrapper in the path, and the agent gets the raw JSON back, every field intact. For the SHA, use git rev-parse HEAD and raw git for anything where the exact object identity matters, never the compacted log.

In practice that bypass is three lines you can lift:

Terminal window
# verify object identity: raw git, never the compactor
git rev-parse HEAD # exact SHA
git log -1 --pretty=%H%n%P # tip + parents (two parents = merge)
# verify an API field: no curl, no wrapper in the path
import json, urllib.request
req = urllib.request.Request(url, headers={"Authorization": auth})
fields = json.load(urllib.request.urlopen(req))["fields"]
assert fields["status"]["name"] == "Done" # literal value, intact

The rule those fixes crystallized is the part worth keeping:

Any tool that sits between your agent and ground truth has to be lossless on the paths that matter. Pretty-printing is for humans. For machine verification, give the agent the raw bytes.

I went and baked that into the toolkit I was building. The git helper module that wraps subprocess calls for my scripts carries an explicit note at the top: raw subprocess wrappers, no compression filtering, because scripts need authoritative output. The token-saving wrapper is banned from any code path where a script is checking state, a SHA, a transition, a payload, anything load-bearing. It is still on everywhere else, still saving the money, still summarizing the 4,000-line test run down to the three lines that failed.

That is the actual resolution, and it does not mean throwing out the optimizer. The optimizer is correct most of the time and pays for itself daily. The resolution is to draw a bright line around the surfaces where exactness is the entire point, and route those through raw bytes with no clever layer in between.