Jake Lundberg

Jake Lundberg

@jakelundberg.dev

Engineering Manager @ Greenplaces. I write about software delivery risk and shipping software that doesn't blow up. Building Merge Lantern, so you know which PRs need senior eyes before they merge. mergelantern.com/?utm_source=bluesky · jakelundberg.dev

100 followers126 following780 posts

Engineers tend to hold software to a higher quality bar than the business asks for. Think event seating...basic chairs solve today's problem. Where engineers are right to dig in is the floor and the fire exits. In software, that's billing, logins, and how you store customer data.

If your team still clicks through the app before every release, reads dependency release notes by hand, or pokes the live site after shipping...I don’t think the answer is to rip those checks out for the same of speed. More than likely the better move is to automate them.

"Slow down" is bad default advice. Speed is the default...keep it simple, ship, get real data, improve. You slow down for exactly two reasons: 1. to simplify something that got too complex 2. because risk crossed a line Everywhere else, fast is how you learn what the business actually needs.

Speed has an ROI. Past a certain level of risk it goes negative...a single mistake can cost more than all the speed you saved. Below that line, move fast and learn. Above it, slow down. A lot of teams never figure out where their line is, so they pick one speed for everything.

So many people wait until they have hours to spend on a project they want to do. Just show up. Screw that! Spend 5 minutes today if that’s all you have. Just show up and do something!

Most of us have seen how AI moved the bottleneck to code review. Some people say removing PR friction is the way to go. I disagree. Review is essential friction that catches the risky stuff before your users do. The trick is applying that friction on the PRs that actually need it.

a founder never feels "tech debt." it's engineer-speak. what they feel is the checkout down at peak, or the senior who just quit. half of getting buy-in to fix it is translating the cost out of engineer terms into the thing they already lie awake about. it's a skill plenty of us are still learning.

the two ways to fail as a new EM are opposites: become an IC with extra meetings, or vanish into calendars with no technical air cover. but they aren't equally likely. the gravity only pulls one way, back toward the keyboard, because that's where the fast feedback is.

"Reduce friction" is half a sentence. Two kinds. Accidental friction...env setup, hunting for docs, chasing permissions...kill it, no argument. Essential friction...the "wait, should we?" before you touch auth or billing...that's the cheapest insurance you have. The skill is telling them apart.

"I'll just build it instead of paying $29 a month" wins almost every argument, for one reason: the ops cost never shows up as a line item. the build is visible. the years of owning it aren't. so it always looks cheaper than it is. you didn't save $29...you hired yourself.

"keep criticism private" is right for the person and wrong for the work. critique of someone, always private. critique of the idea has to happen out loud, or you quietly teach the team that challenging anything is rude. you can protect people without making the work un-challengeable.

a paved road is a risk sorter. automate the common path and the 80% that stays on it is probably fine...not guaranteed, just better odds. which means almost all your real risk now sits in the 20% that left the road, the novel work with the least automation around it. point your attention off-road.

a PR that has sat as a "draft" for 25 days with failing CI and no activity is not work in progress. it is a stale PR wearing a draft label as camouflage. draft is a status someone set once, not proof the thing is still being worked on.

you can tell within weeks which new manager you got. the ones who show up certain they already know what's broken...everything feels on fire. the ones who show up asking questions, things just run smoother. the team always knew the real problems. listening was the only thing that unlocked them.

everyone says the boring layer is a moat because AI fumbles it. that's not quite it. deploy, data, migrations, security...that's where risk actually concentrates, so it's where ownership and judgment matter most, because it's where things break. boring is a moat because it's where the risk lives.

Startups list a dozen traits they want in engineers...comfort with ambiguity, self-direction, speed over perfection. They're all the same trait wearing different hats: can you make a good call when nobody hands you the call. Judgment in the absence of instruction. That's the whole job.

nobody drifts toward more management. new managers slide back to code because it has a fast feedback loop...it compiles, it ships, you did something today. management's payoff is slow and murky, so you retreat to what pays off by end of day. balance isn't a point you find, it's a pull you fight.

line count tells you how long a PR review will take, not how much damage the change can do. a 2,000-line refactor is a long read. a 200-line change in billing or a migration is a blast radius. keeping PRs small is certainly helpful, but it's not a silver bullet to reducing risk.

you can model "I don't know" all day. doesn't matter. the first time someone gets burned for admitting they were lost, the whole team learns to go quiet for months. safety isn't saying you value it. it's never once punishing it.

writing made me a better engineer, not just a more visible one. you can't explain a messy idea simply until you actually understand it. forcing yourself to write it down clearly is the same muscle as shipping a small PR...you have to understand the shape of the thing before you can make it small.

most decisions stuck in "pending alignment" for weeks are reversible. that's the part everyone forgets. you can ship the simple 80% version behind a feature flag, see if it's on track, and kill it if it's not. planning paralysis is usually a reversible decision treated like a one-way door.

fighting the code teaches you how systems break. owning what you shipped and getting burned teaches what's worth protecting. AI mostly takes the first away. the sneaky risk is it takes the second too...code shipped so fast nobody really owns it, so neither lesson ever lands.

"fire fast, attitude over talent" is dangerous in the wrong hands. a leader who isn't good at healthy conflict can't tell a toxic person from a valuable one who pushes back. so the slogan purges the dissenters. firing fast is easy. telling toxic apart from uncomfortable is the real skill.

Last week I showed my risk filter flagging a dark-theme CSS change as touching auth, billing, and secrets. Wrong, for a dumb reason...it decides what's "sensitive" by matching file paths against sensitive-sounding names. So how should it actually know? 🧵

View full thread

The version that fixes this needs real incident data. But I won't have that data until people use the thing. That's the bind...the simple version is what I can launch, but the good version only exists on the far side of people using the simple one.

So I made the call...ship simple anyway, treat its mistakes as the data. The bet, on purpose, is that it's useful enough while it's sometimes wrong to earn its way to better. "Wait until it's good" quietly becomes "never shipped." When has shipping rough to learn paid off for you?

Last week I showed my risk filter flagging a dark-theme CSS change as touching auth, billing, and secrets. Wrong, for a dumb reason...it decides what's "sensitive" by matching file paths against sensitive-sounding names. So how should it actually know? 🧵

View full thread

Option 2: CODEOWNERS, reusing the ownership signals already in the repo. Option 3: incident history, learning which parts of the codebase actually caused problems, whatever they're named. Most accurate by far.

My call for v1 is the simple one: name matching + optional config, CODEOWNERS close behind, incident history later. And I'm nervous about it. It leans on people naming files conventionally (they don't) and filling out a config (they might not).

Last week I showed my risk filter flagging a dark-theme CSS change as touching auth, billing, and secrets. Wrong, for a dumb reason...it decides what's "sensitive" by matching file paths against sensitive-sounding names. So how should it actually know? 🧵

"nobody's buying it" is two completely different problems wearing the same face. one is "they don't want it." the other is "they don't know it exists." marketing only fixes the second...if nobody wants the thing, better distribution just helps more people find out faster.

alert fatigue is silent failure wearing a different mask. instrument everything and the one alert that matters gets scrolled past with the other 200. nobody saw it break...not because it was quiet, because it was buried. too loud and too silent lose the signal the same way.

moving fast tells you "if quality slips, we fix it next sprint." that works for the misses you can see. the dangerous ones don't wait for next sprint though...they show up as an incident, on their own schedule. the corner you cut and the bill for it rarely land in the same week.

a feature flag's default flipped a dead feature back on, and we didn't catch it...a user did. nothing was watching for a code path that hadn't run in a year suddenly running. you don't need much. just something that raises a hand when dormant code wakes up.

the clearest signs of real engineering discipline live at the edges of the work. how did a team decide to build this at all? and after shipping, did they check how it got used and iterate? clean code is cheap now. the decision going in and the follow through coming out are the hard parts to fake.

1/ Someone on my team deleted a feature flag that had been off for over a year. It looked completely safe to remove. It turned a feature back on. How "cleanup" became an accidental deploy 🧵

View full thread

6/ A flag off for a year isn't dead. It's dormant. And dormant is worse, because dead means someone confirmed it's safe to remove. Unused isn't the same as unimportant.

1/ Someone on my team deleted a feature flag that had been off for over a year. It looked completely safe to remove. It turned a feature back on. How "cleanup" became an accidental deploy 🧵

View full thread

4/ Tracing it was brutal, for two reasons that both come back to the flag: it didn't exist anymore (nothing to find), and the old targeting hit only a subset of users (couldn't reproduce until we found the group).

5/ The lesson: deleting a feature flag doesn't remove a decision. It hands the decision to whatever your code falls back to. Don't know that fallback for every place it's read? You're not cleaning up, you're shipping a change and hoping.

1/ Someone on my team deleted a feature flag that had been off for over a year. It looked completely safe to remove. It turned a feature back on. How "cleanup" became an accidental deploy 🧵

2/ The flag was gone, but there was still code reading it. With the flag deleted, that read fell back to the code's default value. And the default was true. A long-dead feature quietly switched itself on.

3/ It collided with another feature that was never meant to run alongside it, and both broke. We didn't catch it...a user did. Nothing was watching for a code path that hadn't run in a year suddenly running.

watched a junior i mentored years ago go from second guessing everything to fully trusting their judgement. most of that shift is evidence...they make calls, the calls work, the proof stacks up. but not all of it. it's a lot easier to trust yourself when someone you respect already trusts you first.

You are up to date.