Skip to main content

The Pinned Dependency Your Security Agent Upgraded Past the Comment It Could Not See

· 10 min read
Tian Pan
Software Engineer

A Spanish customer complained that her annual renewal had been billed a day early. The support ticket bounced through three queues before it landed in front of an engineer who recognized the smell: a date-formatting regression, European cohort only. He ran git log against the date-formatting module and found nothing. The module had not been touched in eleven days. What had been touched, eleven days earlier, was its package.json — a lodash bump from 4.17.20 to 4.17.22, opened by a security agent, approved by the on-call, merged without comment.

Two lines above the version string, in the same file, was a comment written eighteen months ago: // do not upgrade — breaks the snapshot tests in date-formatting, see FRONT-2418. The security agent had not read it. Or, more precisely: the security agent had read the entire file, but its prompt instructed it to find vulnerable version strings, not to weigh the comments around them. The comment was load-bearing institutional knowledge. The agent treated it as scenery.

This is a coordination failure between two systems that did not know they were colliding. The security agent was doing its job. The original engineer who wrote the comment had done his job. The feature-development agent that respected the pin every time it touched the file was doing its job. Nobody had decided whose job it was to mediate between them.

Two agents, one repository, no protocol

The team's setup was, by 2026 standards, unremarkable. A security-policy agent ran on a nightly schedule, scanning the dependency graph for known CVEs and filing PRs to bump versions past them. A feature-development agent picked up Jira tickets each morning, opened a branch, and produced a PR against whichever package the ticket touched. Both agents read the same source of truth — the repository — and both wrote to it through the same pull-request mechanism.

What they did not share was any notion of each other's preconditions. The security agent's prompt encoded a single goal: upgrade vulnerable dependencies. The feature agent's prompt encoded a different goal: implement the ticket. Neither prompt mentioned the other agent. Neither agent had read the other agent's prompt. Each agent's planner saw the codebase as if it were the only writer.

Version control is good at detecting that two writers touched the same line. It is bad at detecting that two writers touched different lines for incompatible reasons. The security agent and the feature agent never produced a merge conflict because they rarely opened PRs against the same package on the same day. The collision happened in a slower register — across weeks, across PRs, against shared institutional knowledge that lived in comments neither agent was trained to honor.

The architectural realization, after the postmortem: a codebase under multi-agent operation is a multi-writer system whose conflict resolution is whatever each agent's prompt happens to encode. There is no shared lock. There is no negotiation. Whichever agent ran first wrote the state the others inherited.

The comment is not documentation, it is a contract

The reviewer who looked at the pinned 4.17.20 line in the original PR — eighteen months earlier — wrote the comment because he had spent four days hunting a date-off-by-one bug in the European-locale snapshot tests. He had isolated the regression to a specific change in how lodash's _.isDate interacted with the locale-aware date parser the team used. He had downgraded to 4.17.20, captured the rationale in the comment, opened ticket FRONT-2418, and shipped a fix. The comment was the only place that knowledge lived in code. The ticket was the only place that knowledge lived in prose. The two were linked by a string that the security agent's prompt did not parse.

This pattern is common and undervalued. Comments next to pinned versions are not commentary about the code — they are the contract that explains why the code is this way and not the other way. They encode a previous incident in compressed form. When an agent (or a human) upgrades the pinned version without reading the comment, it does not just lose context. It overrides a decision the team made before the agent existed.

The security agent's prompt could have included an instruction to read comments adjacent to pinned versions and to escalate any non-empty comment as a precondition rather than as background. It did not. The team built the agent against the assumption that pinned versions were either historical caution (safe to upgrade past) or active risk (CVE), with no third category for "pinned for a documented reason." The third category is the largest of the three in any mature codebase.

CI cannot save you when the test that would catch you is quarantined

The PR passed CI. This is the part of the incident the team found hardest to talk about, because it meant the safety net they thought they had was not there.

The snapshot tests for the date-formatting module had been quarantined two months earlier. They had started failing intermittently after a Node version bump that the team eventually identified as a timezone-handling change in the test runner. The fix would have taken a day. The team was mid-quarter, two engineers down, and the path of least resistance was the path the team took: mark the suite as quarantined, file a ticket to un-quarantine it, move on.

Quarantining a flaky test suite is the right move when the alternative is treating the suite as a coin flip on every merge. The wrong move is forgetting that the quarantined suite is the regression detector for a specific class of bug. From the moment the quarantine flag flipped, every PR that touched the date-formatting module's dependencies had a hole in its safety net that nobody could see in CI's green checkmark.

The security agent's PR walked through that hole. The on-call reviewer, who batch-approved security upgrades because the prior eighteen months of security upgrades had been routine, did not click into the file to read the line two above the version string. The merge button was green. The merge button is always green.

Loading…
References:Let's stay in touch and Follow me for more thoughts and updates