← Back to blog2026-08-15

Don't leave the second-order pin to memory

A release helper correctly refreshed a public source URL, but did not tell the agent to update the SHA-256 pin that CI uses to guard hosted work. The fix was not another reminder; it was making the producer print its downstream pin and turning a stale assertion into a regression tripwire.

The problem

Agent workflows fail in a boring way: not because the agent ignored the instruction, but because the instruction was incomplete.

Our case was a release-helper bug. The OSS sync script published and verified a public source tag, then rewrote the production compose file so POSTFLOP_SOLVER_SOURCE_URL pointed at the immutable source commit for the deployed solver. That compose file is also one of the files pinned by the GitHub Actions cost guard. Change the URL and its SHA-256 changes too.

The script's Next: block listed the files to commit after the sync. It did not list the cost-guard pin. An agent could follow the printed checklist exactly, pass the local gates, and still fail later in hosted CI at GitHub Actions cost policy. Standard production admissions are non-refundable once reserved, but that is not what happened here: run 31818321915 was a hotfix dispatch and failed before Reserve production admission, so it consumed hosted runner time but no standard daily slot.

The reusable lesson is narrow but important: when a tool mutates a file that another guard pins by content, the tool must output the downstream pin update itself. Do not leave that second-order edit to memory, reviewer folklore, or a separate doc.

The mechanism

Content pins are useful because they are dumb on purpose. A regex can be argued with; a reviewed digest either matches or it does not. Our hosted-cost policy pins the exact workflow and local files it allows, and fails closed when an unreviewed payload appears.

HOSTED_LOCAL_FILE_SHA256=(
  'scripts/tests/db-observability-guard.sh|...'
  'deploy/docker-compose.prod.yml|...'
)

That style is defensible for expensive hosted runners and production release paths. It prevents a small YAML spelling change, helper rewrite, or build-context expansion from silently adding new work to CI.

But content pins create a second object that must move with the first. In our case, oss-sync.sh already knew the exact moment it edited deploy/docker-compose.prod.yml. It also had the local file bytes in hand. Asking a later agent to remember, "also repin the cost guard," was the weak link.

What changed

The fix moved the instruction to the producer. After the source tag is verified and the compose file is rewritten, the script now prints the next SHA-256 to place in the cost guard:

2. REPIN in the same commit: set the deploy/docker-compose.prod.yml SHA-256 in
   scripts/tests/github-actions-cost-guard.sh to $(shasum -a 256 deploy/docker-compose.prod.yml | awk '{print $1}')

That is a small change, but it changes the workflow shape. The agent no longer has to infer a hidden dependency between "source URL changed" and "hosted-cost manifest changed." The command that created the first change now emits the second change as part of its own receipt.

The guard pin was then updated to the reviewed bytes of deploy/docker-compose.prod.yml. Separately, the same patch updated the digest for scripts/tests/db-observability-guard.sh, because that guard also changed.

Do not just delete stale assertions

The second half of the patch is the part I would copy into any agent-heavy codebase. A database observability guard used to require DROP EXTENSION IF EXISTS pg_stat_statements in migration 0065's down file. That was the wrong inverse: production preloads pg_stat_statements, and the up migration uses CREATE EXTENSION IF NOT EXISTS. A rollback could drop an extension this migration may not have created.

The lazy fix would be to remove the old assertion. The better fix was to invert it:

if grep -Fq 'DROP EXTENSION IF EXISTS pg_stat_statements' "$down"; then
  echo "0065's down migration must NOT drop pg_stat_statements" >&2
  exit 1
fi

That turns yesterday's false requirement into tomorrow's regression test. The difference matters with agents: a future repair pass may grep for symmetry, see CREATE EXTENSION in the up migration, and "restore" the destructive down step. The inverted check makes that attractive-looking edit fail immediately.

The pattern to copy

For every generated or semi-generated release artifact, keep three facts close together:

QuestionWhere it belongsFailure mode if missing
What file did the tool mutate?The producer's own outputThe agent commits an incomplete set
Who pins or reviews that file by content?The same output, with the exact downstream fileLocal proof passes; hosted proof fails
What bad old shape must never return?An inverted guard, not deleted proseA later "cleanup" reintroduces the bug

The point is not to make agents smarter. The point is to make the workflow less dependent on smartness. If a tool can compute the next hash, print the hash. If a stale check was wrong, replace it with the forbidden shape. If a hosted guard burns time or release capacity, make it fail as early as the platform allows.

Recap

  1. A content pin is a contract. It is only useful if every producer that changes pinned content knows how to move the pin.
  2. Receipts beat reminders. Put the repin command in the script output at the moment the bytes change.
  3. Invert bad assertions. Do not delete a stale check when its old shape is dangerous; make that shape explicitly forbidden.
  4. Design for literal agents. A workflow that only succeeds when someone remembers the hidden second step is not automated yet.