Two months of silence, then a Tuesday.

It was a Tuesday. The flow had been sending approval notifications every morning for nine weeks, untouched, silent, reliable. The way automation is meant to work. You half-forgot you built it.

Then a colleague renamed a SharePoint column. "Status" became "Request Status". A reasonable tidy. They had every right to do it. Nobody told you, because nobody thought to; why would a column rename concern anyone outside their team?

By Thursday, somebody in procurement flagged that three approvals had disappeared. The requests were stuck in a state that no longer existed. You opened the flow. The run history showed failures you'd never been emailed about, because the connector had degraded gracefully to "no matching items" rather than raising an error. The flow was working exactly as designed, returning nothing, writing nothing, alerting nothing, because the column it was looking for no longer existed by that name.

You fixed it in twenty minutes. You also spent the next hour wondering how many other flows were silently doing nothing because the data had shifted underneath them.

Your flow didn't break. It held up its end of the deal.

This is the moment the real lesson arrives, and most people miss it. The reflex is to call this a bug: a flaky platform, a careless colleague, bad timing. Those framings are all self-soothing. They imply the flow was good and something external failed it.

The flow wasn't good. The flow had an undocumented assumption baked into it, and the assumption didn't hold. That isn't a failure of the environment. It's a design choice you made without realising you were making it.

Every reference is a bet.

Every reference your flow makes to the outside world is a bet. Not a vague one, a specific contractual one, with terms you didn't read and didn't sign:

  • I bet this column will still be called "Status" six months from now.
  • I bet this folder path won't move.
  • I bet this dropdown value won't get retitled, re-cased, or replaced.
  • I bet this file naming convention won't evolve.
  • I bet the person who owns this list won't change jobs.

Some of those bets are reasonable. Some are wildly optimistic. None of them are written down, which is the actual problem. If you'd been handed a list of your flow's assumptions on day one, you would have noticed that half of them depend on the behaviour of people you don't manage, doing work you can't see, in systems that aren't yours.

Where the fragility lives

Three places in Power Automate swallow these bets quietly enough that you don't notice them.

Display names versus internal names

When you bind a SharePoint action to a column, the connector stores the display name, the human-readable one, the one anyone can rename from the list settings panel. SharePoint also has an internal name, assigned once at column creation, immutable thereafter. The internal name is what the REST API uses. The connector doesn't default to it. This is a platform choice Microsoft made, and it's worth knowing: anywhere your flow references "Status" from the dynamic content picker, it's actually saying "give me whichever column is currently called Status." Someone changes the display name, and the flow is now looking for a column that doesn't exist.

You can work around this with the "Send an HTTP request to SharePoint" action, which lets you reference fields by their internal name via REST or CAML. It's more setup. For flows that touch anything financial, compliance-adjacent, or audit-tracked, it earns its place.

Hard-coded paths

Typing a full folder path into a trigger action, sites/Finance/Shared Documents/Invoices/2026/Q2, creates a reference that lives inside the flow, repeated every time you built a similar one, invisible to anyone who comes after you. When the folder moves, the flow fails silently. When you need to point the same logic at a different site for a different team, you rebuild instead of reconfigure.

Environment variables exist for a reason. Even in solutions you'll never formally manage, the habit of extracting paths and URLs into variables is one of those things that costs five minutes up front and saves you half a day six months later.

Exact-value conditions

A branch like If Status equals "In Progress" looks solid until the list owner decides the stage should be called "In Review," or someone introduces a version with a trailing space, or a new status joins the workflow that your condition doesn't account for. The branch doesn't fail. It quietly routes the record to the else clause, where you didn't design for anything to happen. Records fall through, nobody notices, and the failure is invisible until somebody asks a question the flow was supposed to answer.

"Contains" is more forgiving than "equals." Driving branching from data you control is more forgiving than from values someone else maintains. More resilient still is letting the data tell the flow what to do, instead of the flow telling the data what the answers had better be.

What defensive design actually looks like

This isn't a call to rebuild every flow. It's a set of habits, cheap to apply on new builds and cheaper to apply at the moment you're already in there fixing something.

Write down what each flow depends on. Not a full specification, a list. Which site, which list, which columns, which folder paths, which exact values the flow branches on, which people's roles the flow assumes are staffed. If that list is longer than you expected, that itself is the lesson. It will also save a future version of you, or whoever inherits the flow, at least an afternoon.

Ask whether each dependency is yours. If it is, it's stable until you move it. If it isn't, you're exposed. Exposure isn't a reason not to use the dependency, it's a reason to know you're using it.

Use internal names where it counts. Not everywhere, not reflexively. For flows running approvals, money, compliance checks, or anything where a quiet failure produces a downstream problem, the extra configuration work is the price of the flow being worth building at all.

Version before editing. Save a copy of a working flow before you touch it. The export-import dance is clumsy, the naming convention is yours to invent, and it will feel pointless nine times out of ten. The tenth time, when you've introduced an error and need to revert at 4pm on a Friday, you will understand why.

The contract you didn't know you signed

Here is the shift, and the part worth keeping if you take nothing else.

Your flow is a contract with the environment it runs in. You wrote the terms when you built it, whether you noticed or not. Most of those terms are unwritten, which means they've never been negotiated with the people whose behaviour they constrain.

Defensive design is making the contract explicit. Choosing which clauses to tighten, which ones to loosen, and which ones to write down because you can't eliminate the exposure. It doesn't make flows unbreakable. It makes the breakage predictable.

The colleague who renamed the column wasn't careless. They were doing their job. Your flow had an undocumented claim on their column name, and the claim wasn't theirs to honour. That's not a bug report. That's a design flaw you get to fix now, before the next rename comes, because it will, and the quiet failure on the other side of it will look exactly like this one.