,

The Lock-in Nobody Signed Up For

Agency lock-in has not gone away – it’s still very much “a thing”. It has moved from the account you could not log into to the pipeline you cannot run.

The old trick

10 or 15 years ago, the move was simple. An agency would set up your AdWords (Google Ads) account, or your Google Analytics property, inside its own account. You paid for the clicks. They owned the history.

When you wanted to leave, you found out what that meant. Years of campaign structure, conversion data and audience lists sat somewhere you had no rights to. Getting them back meant asking nicely, paying a fee, or starting again from nothing.

Most clients learned the lesson. Our default has always been that the client owns the accounts. We’re invited as users rather than owners. The obvious version of the trick is largely gone.

The modern version

Today the asset is not an ad account. It is a data pipeline: a dbt project in a Git repository, deployed by CI to a cloud project, feeding reports the business runs on every week.  This is a business critical data pipeline.

We’re supporting a large organisation to maintain several of these pipelines. On paper, the client was in a good position. The repositories sat in the client’s own GitHub organisation. The data sat in the client’s own cloud projects. Nobody could say the client did not own the code.

Ownership turned out to be the easy part. Running it was another matter. The client owned the code, but could not ship a change to production without the previous agency, and nobody had noticed because this was not an obvious behaviour.

Dev green, prod red

The first change we pushed was small: a fix to how the deploy workflow authenticated to the cloud project. The workflow ran, dev went green, and the deployments panel still showed production in red.

The natural reading was that our fix had worked for dev and broken for prod. It had not. The red mark was 5 months old.

Scrolling back through the production deployment history told the real story…

Every production release for more than 18 months was marked failed.

The pipeline had been running the whole time. Reports had gone out every Monday. But no updates had reliably reached production through CI since early 2025.

The unravelling

What followed was a morning of peeling back layers. None of them was hard on its own. Together they explain how a pipeline can look healthy for over a year while production stands still.

Layer 1: production only deploys on a release

The repository used a sensible two-gate design. It also means a perfect fix can sit on main indefinitely without ever reaching production, if no release is cut.

Layer 2: the release tool was silently skipping everything

A convention over configuration choice meant that the commit process started well but easily fell down.

Technical detail you can skip if you want: Release Please only counts commits whose message starts with a conventional prefix such as fix: or feat:. The pull requests had those prefixes in their titles, and a linting check confirmed it. But the repository’s squash merge setting used the commit message, not the pull request title, so the prefix was dropped at the moment of merging.

Every fix merged that way was invisible to the release tool. No error, no warning: just a quiet “no user facing commits found” in a log nobody read.

Not using fix: resulted in a quiet, empty release.

Flowchart showing the process of merging code changes to production, highlighting the impact of a missing prefix on release outcomes. It outlines steps such as merging to main, checking for a prefix, and the distinction between dev and production deployments.

Every merge reached dev, so dev always looked healthy. Production depended on the release tool seeing a prefix that the merge setting had already thrown away.

Layer 3: you cannot fix the setting without the right role

The obvious fix is a repository setting: make squash merges use the pull request title. Changing it needed an admin on the client’s own repository.

The workaround was to edit the commit message by hand in the merge dialog. It works, but it relies on everyone remembering, which is how the problem started. A stark reminder…

Layer 4: main is protected, and so it should be

To trigger a release, we needed one correctly prefixed commit on main. So: a branch, a pull request, an approval from the client’s engineer, a merge.

The release tool woke up, opened the release pull request with both fixes in its changelog, and that needed its own approval too. Two reviews, two merges, and – drum roll –

production finally deployed through CI for the first time in more than a year and a half.

The release pull request, incidentally, still announced itself with a bot named after the outgoing agency. A small thing, but a fair summary of whose process this still was.

Before you change agencies

In fairness to the outgoing agency, the design was solid but made assumptions and had non-obvious fragility (unless you’re new, and/or external). The effect on the client is the same either way. Owning the code is not the same as being able to run it.

If you are taking a data pipeline back from an agency, check these while they are still around to answer:

  1. Can someone on your side ship a change to production, today, without the agency? Not in theory. Do it, with a trivial change, end to end.
  2. Does the deployment history agree with what is live? Look at the production history, not just the latest status. A run of failures is a finding.
  3. Who holds admin? On the repository, the cloud project, the service accounts and the identities CI deploys with. If any of these sit with the agency, the handover is not finished.
  4. What can fail? Release tools, scheduled jobs and alert emails all have quiet failure modes. Find out what happens when each one does nothing.
  5. Do the numbers match the source? Pick one product and one day, and reconcile the report against the raw data. It takes minutes, and it is the only check that tells you the output is right rather than merely present.

The old lock-in kept your data in someone else’s account. The new one keeps your ability to change it in someone else’s head. Both are fixed the same way: by doing it yourself, once, before you need to.