michaelheap.comThoughts on leadership, code and how to fix odd edge cases in tools (not necessarily in that order)2026-02-26T14:58:41Zhttps://michaelheap.comMichael Heapm@michaelheap.comIn the spotlight2026-02-26T14:58:41Zhttps://michaelheap.com/in-the-spotlight/<p>I've spent most of my career on the periphery. Building the tools, documentation and platforms that make it possible to build the product that customers pay for. I remember fighting tooth and nail for headcount. Lobbying to get some time on the company all-hands to make sure people know that we exist. Being worried that when cuts inevitably come, the teams I work with would be first on the chopping block.</p>
<p>Now, I'm in the spotlight. I'm the product leader for the product that makes the majority of our revenue. I fought so long to be in the spotlight, and now that I'm here I'm struggling to bear its weight.</p>
<h2 id="the-weight-of-the-spotlight" tabindex="-1">The weight of the spotlight</h2>
<p>Working on a revenue-generating product is often treated as a privilege. The work is visible. Impact is easier to point to. Success maps cleanly to business outcomes.</p>
<p>But that visibility comes with scrutiny.</p>
<p>Every decision is observed, evaluated, and challenged - by leadership, sales, marketing, finance, customer success, and often customers themselves. The closer your work is to revenue, the more people feel entitled to an opinion about it.</p>
<p>I thought I'd spend my time adding new capabilities to the product, but I've learned that in the spotlight you don't ship features. You defend them.</p>
<h2 id="revenue-%3D-restrictions" tabindex="-1">Revenue = Restrictions</h2>
<p>The closer your work is to the revenue, the less freedom you have on your own destiny. With so many people's careers relying on your product (internally, and within your customers) you're subject to a large list of constraints:</p>
<ul>
<li>Features promised to sign a deal</li>
<li>Existing customer expectations</li>
<li>Revenue targets from the business</li>
</ul>
<p>As you try to deliver all of these, you also need to maintain stability for your existing customers. You can't break anything, ever. Experimentation becomes expensive. Reversibility matters more than elegance.</p>
<p>You're walking down a narrow corridor under a bright light, stepping carefully do you don't touch the wall on either side. As you walk, people are shouting their opinions on pricing, roadmap priorities and market positioning.</p>
<p>And you can't ignore those opinions. Each statement is framed as risk. If we don't ship feature X, customer Y will churn. If we charge $10,000, we'll lose the deal. We need to shift messaging to CIOs to capture that budget.</p>
<h2 id="system-breakdown" tabindex="-1">System breakdown</h2>
<p>Over time, everything grinds to a halt.</p>
<ul>
<li>People ship the safe thing, not the impactful thing</li>
<li>Timelines are extended so that we can "underpromise and overdeliver"</li>
<li>People optimize for defensibility rather than learning</li>
</ul>
<p>This isn't your team doing the wrong things. They're reacting to the risk in the system with a rational response. No-one wants to be associated with a loss of revenue due to a bet they made. So we keep doing the safe things.</p>
<h2 id="the-trade-off" tabindex="-1">The trade-off</h2>
<p>When I think back to my time on auxiliary teams, there was no spotlight. We'd occasionally get feedback from a customer, but overall our destiny was our own. We could make the big bets, but when they were successful no-one really understood just how impactful the work was.</p>
<p>For auxiliary teams, success looks like nothing breaking, and no-one complaining. They keep the lights on all while longing for the spotlight themselves, not realising that there's a cost to pay.</p>
<p>Revenue teams trade autonomy for visibility.<br />
Auxiliary teams trade visibility for autonomy.</p>
<p>Neither is easier. They just fail in different directions.</p>
<h2 id="learning-to-carry-the-light" tabindex="-1">Learning to carry the light</h2>
<p>I don't think the answer is to work in the shadows. Revenue work is what pays the bills, and the scrutiny exists for real reasons. It's rational for people to protect it.</p>
<p>But the cost of that protection is often invisible: slower learning, narrower ambition, and teams that mistake <em>defensibility</em> for <em>good judgment</em>. When everything is framed as existential risk, we stop asking what’s worth building and start asking what’s hardest to argue against.</p>
<p>The work, I’m learning, isn’t just product strategy. It’s systems design. Creating space where teams can make bets without being crushed by hindsight. Separating real risk from imagined risk. Absorbing pressure so it doesn’t flatten everyone else.</p>
<p>That's the role of a leader. To make space. To bring clarity. To be the filter that brings signal to the noise.</p>
<p>The spotlight isn’t going away. But maybe it doesn’t have to blind us.</p>
Choose your losses2026-02-18T17:13:44Zhttps://michaelheap.com/choose-your-losses/<p>Strategy is choosing what you’ll be bad at.</p>
<p>I’ve been revisiting Michael Porter’s work on strategy, and the point that's stuck with me is this: trying to be “the best” is usually a trap. “Best” often means you’re competing on the same dimensions as everyone else. The same benchmarks, same dashboards, same playbook. You can lead for a while, but the moment everyone copies what works, yesterday’s advantage becomes today’s expectation.</p>
<p>If you want to avoid the race to the bottom, don't try and compete on doing the same things as everyone else, but better. Do the things that you're uniquely positioned to do that are hard to copy for your competitors.</p>
<h2 id="stop-confusing-%E2%80%9Cbetter%E2%80%9D-with-%E2%80%9Cdifferent%E2%80%9D" tabindex="-1">Stop Confusing “Better” With “Different”</h2>
<p>Companies often confuse differentiation with excellence. Maybe you have more features, or higher quality, or better customer support. Those <em>are</em> differentiators, but guess what:</p>
<ul>
<li>Your competitors are building those features right now</li>
<li>You're only one bad bug away from reputational damage</li>
<li>Your best support staff will leave (potentially for your competitors)</li>
</ul>
<p>The things that we say make us different are mostly <em>operational effectiveness</em> rather than <em>strategic differentiation</em>.</p>
<p>Strategic differentiation is the thing that's hard to copy. It's a set of choices that reinforce each other to build an advantage. Copying a single choice that you've made doesn't help the copier. They'd have to copy the whole system (and accept the trade-offs that you've made along the way).</p>
<p>This is how you differentiate yourself from the market.</p>
<p>The uncomfortable part is you have to name what you’re <em>not</em> going to optimize.</p>
<p>If you can’t say what you choose to be bad at, you don’t have a strategy, you have ambition.</p>
<h2 id="choose-your-customer" tabindex="-1">Choose Your Customer</h2>
<p>Let's build a sample use case to really drive the point home. There has been a proliferation of observability tools over the last couple of years. Thanks to open standards (thanks, OpenTelemetry!) it feels like a new competitor has popped up every other week and the cost of switching is lower than it's ever been. Competing on better pricing or a nicer UI is a dead end.</p>
<p>Many of the tools out there position real-time data processing as a benefit. The event happens, and less than 5 seconds later you can see it in your dashboard. Your system can do it in 3 seconds, so you're the market leader for those looking for speed.</p>
<p>Eventually though, someone else will do it in 2.5 seconds. Then 2, then 1. Suddenly your advantage is gone.</p>
<p>Instead of competing on the same metric that everyone uses, what if you explored other use cases:</p>
<ul>
<li>These tools have 30-day retention -> We'll provide 365-day retention at the same granularity (targeting compliance) -> Storage and retrieval of the data takes up to 10 minutes</li>
<li>Sending so much data across the internet is expensive -> Our product can't show realtime, fine-grained data, but we have an agent that runs daily and reduces data egress costs by 90% (targeting FinOps teams) -> Additional deployment complexity for customers</li>
<li>We pride ourselves on performance -> We're slightly slower to ingest, but we guarantee exactly once ingestion (targeting regulated industries) -> Our sales team finds it hard to demo</li>
</ul>
<p>Each of these use cases forces architecture, pricing and go-to market decisions. They are hard to imitate, and hard to undo. If sales asks for realtime dashboards, the strategy answer isn’t ‘maybe later.’ It’s ‘no’ unless we’re willing to unwind the system that makes our advantage.</p>
<p>If you're successful, your competitors will try to copy you without having the correct foundations. They built an architecture around ingestion speed and are trying to pivot to long-term retention with the wrong technologies, and the wrong team.</p>
<p>It's important to emphasize that these are <strong>trade-offs</strong>. If the market values speed over retention, you don’t get to “pivot” to instant ingestion without undoing the choices that made you different. You can't do everything <em>and</em> be differentiated.</p>
<h2 id="build-a-stack%2C-not-a-skill" tabindex="-1">Build a Stack, Not a Skill</h2>
<p>Most career advice is generic. "Be a great communicator". "Always deliver on time". When I'm reviewing applications as a hiring manager, I've seen all of the generic traits by the fifth or sixth application.</p>
<p>Instead of saying the same as everyone else, build a specific combination of traits that sets you apart.</p>
<p>To build your stack of traits, choose one item from each of these categories:</p>
<ol>
<li>
<p>Domain / context (where you have unusual understanding)<br />
Examples: platforms, regulated industries, developer workflows, migrations, pricing, enterprise buying, support realities.</p>
</li>
<li>
<p>Craft (how you work)<br />
Examples: writing, systems thinking, facilitation, debugging, prototyping, negotiation, research.</p>
</li>
<li>
<p>Leverage (how it spreads)<br />
Examples: internal enablement, templates, docs, workshops, tooling, a repeatable process, a network.</p>
</li>
</ol>
<p>Your uniqueness usually lives in the combination, not a single skill.</p>
<p>Example: “Deep platform context + crisp writing + enablement templates”<br />
becomes: someone who makes complex platform decisions simple and repeatable across teams.</p>
<p>As mentioned earlier, there are trade-offs each time you make a decision. Name the trade-off you're willing to make consistently:</p>
<ul>
<li>I'll lose five small deals to save one large deal</li>
<li>I care more about getting to what’s true than about everyone agreeing</li>
<li>I prioritise iteration speed over upfront certainty</li>
<li>We optimise for consistency, not autonomy</li>
<li>I aim to understand 25% of Product, Engineering and Marketing rather than 80% of Product</li>
</ul>
<p>Personally, I am the person you call when something is on fire, because I can rapidly gain context from both a technical and business perspective. To enable this, I will happily be mediocre at long-horizon planning and stakeholder consensus-building.</p>
<h2 id="the-pattern-shows-up-everywhere" tabindex="-1">The Pattern Shows Up Everywhere</h2>
<p>Being unique is a strategy that works in every industry.</p>
<p><strong>IKEA</strong> optimise for low prices and immediate availability, at the expense of service through flat-pack, self-assembly, and “good enough” furniture.</p>
<p><strong>FedEx</strong> optimise for reliable, on-time delivery rather than aiming to be the cheapest option.</p>
<p><strong>Nespresso</strong> optimise for convenient premium-at-home coffee, at the expense of a closed pod ecosystem and a higher cost per cup.</p>
<p>That's how you build an advantage, not by competing in a race where the prize is eventually becoming everyone’s baseline. Strategy is choosing what you’ll refuse to be great at - and then living with that refusal long enough that it becomes your advantage.</p>
Never leave on a no2026-02-13T03:52:05Zhttps://michaelheap.com/never-leave-on-a-no/<p>One of the biggest mistakes I see new managers make is how they manage disagreement with their boss.</p>
<p>They do one of two things:</p>
<ul>
<li>They nod along, but disagree quietly and disengage</li>
<li>They keep arguing until they run out of time and the meeting ends awkwardly, with no real decision</li>
</ul>
<p>Both options leave their boss frustrated.</p>
<p>Something that's worked well for me is that I <strong>never leave a conversation on a no</strong>.</p>
<p>It doesn't mean we always agree. It means that we're aligned on what the next steps are.</p>
<h2 id="disagreeing-behind-closed-doors" tabindex="-1">Disagreeing behind closed doors</h2>
<p>I believe that it's everyone's responsibility to challenge bad ideas. You should challenge ideas when:</p>
<ul>
<li>You have information that the person you're talking to doesn't</li>
<li>You're accountable for the outcome</li>
<li>You believe a decision will materially harm your team, customers or business</li>
</ul>
<p>How you challenge is important. Shouting in a meeting room with 15 other people won't get you anywhere. Instead, argue with your boss <em>in private</em> where there is space to listen and to be heard.</p>
<h2 id="know-when-to-stop" tabindex="-1">Know when to stop</h2>
<p>There's a difference between advocacy and infinite debate. Knowing when to stop pushing and start aligning is a skill.</p>
<p>Early in a discussion you can push hard:</p>
<ul>
<li>Ask clarifying questions</li>
<li>Share concrete risks</li>
<li>Offer alternatives, not just objections</li>
</ul>
<p>But at some point, you'll feel a shift in the conversation. You start getting repeated answers. The trade-offs have been heard. The decision has been made.</p>
<p>The most senior person in the room decides when the argument is over. Your job is to notice.</p>
<p>Missing that moment once is forgivable. Missing it repeatedly gets you labeled “difficult,” regardless of the quality of your ideas.</p>
<p>Disagreement is a tool for improving decisions, and once the decision is made your job changes.</p>
<h2 id="what-%E2%80%9Cnever-leave-on-a-no%E2%80%9D-actually-means" tabindex="-1">What “never leave on a no” actually means</h2>
<p>Before the meeting ends, alignment has to be explicit.</p>
<p>You should be able to say one of these - and mean it:</p>
<ul>
<li>“I disagree, but I understand the decision and I’ll execute it.”</li>
<li>“I still have concerns, but given the trade-offs, this is the direction we’re taking.”</li>
<li>“Let’s try this for X weeks and revisit with data.”</li>
</ul>
<p>A clean disagreement that ends in commitment builds trust. A fuzzy disagreement that lingers destroys it.</p>
<p>From your boss’s perspective, there are few things more dangerous than a manager who:</p>
<ul>
<li>Appears aligned in the meeting</li>
<li>Signals doubt afterward</li>
<li>Executes half-heartedly</li>
</ul>
<p>If you can’t commit, say so <em>in the room</em>. Don’t carry a private veto.</p>
<h2 id="sharing-with-your-team" tabindex="-1">Sharing with your team</h2>
<p>It's easy to paint leadership as the bad guys.</p>
<ul>
<li>"This wasn't my call"</li>
<li>"I don't agree, but..."</li>
<li>"Leadership wants us to..."</li>
</ul>
<p>It feels like solidarity, but it teaches your team that alignment is optional.</p>
<p>Your team works for <em>you</em>, not leadership, and they need to know what they need to do be successful in your business.</p>
<ul>
<li>What was the decision?</li>
<li>Why did we decide this?</li>
<li>What does success look like?</li>
</ul>
<p>You don't have to say that you're fully behind the idea, but you <em>do</em> need to communicate that this is the direction.</p>
<blockquote>
<p>“There were other options, and we discussed them. This is the direction we’re taking, and here’s how we’ll make it successful.”</p>
</blockquote>
<p>If you can't stand behind the decision at all, you need to get back in a room with your boss and talk until you can.</p>
<h2 id="your-real-job" tabindex="-1">Your real job</h2>
<p>Your job isn’t to win arguments.<br />
It’s to reduce uncertainty.</p>
<p>Argue fiercely when it matters.<br />
Notice when the argument is over.<br />
And never walk out of the room without a clear answer to one question:</p>
<p><strong>“What are we doing next?”</strong></p>
Update the AUR from GitHub Actions2026-02-05T13:45:31Zhttps://michaelheap.com/actions-update-aur/<p>I maintain <a href="https://github.com/mheap/trello-cli">trello-cli</a>, which is also published to the Arch User Repository (AUR).</p>
<p>Each time the package is published, I needed to update the <code>PKGBUILD</code> with a new version and <code>sha512sum</code>. I'm no longer an Arch user (I miss it, but MacOS works so much better for my current role) and so I don't have <code>makepkg</code> readily available.</p>
<p>I had an <a href="https://michaelheap.com/ideas">idea</a> that updating the <code>PKGBUILD</code> could be done with GitHub Actions:</p>
<blockquote>
<p>Updating an AUR package for a node based tool could be automated using GitHub Actions.<br />
Each time a release is tagged, the <code>pkgver</code> and <code>sha512sums</code> fields in the <code>PKGBUILD</code> need updating, and the repo needs pushing to the <a href="https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD">https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD</a><br />
The <code>sha512sum</code> needs to be calculated from the published NPM package, so we'd need to wait until the version is available, download it from the registry and then calculate the checksum.</p>
</blockquote>
<p>Here's how I did it:</p>
<ol>
<li>
<p>Generate an SSH keypair:</p>
<pre class="shiki nord" style="background-color: #2e3440ff; color: #d8dee9ff"><div class="language-id">bash</div><div class="code-container"><code><div class="line"><span style="color: #D8DEE9FF">ssh-keygen -t ed25519 -f </span><span style="color: #81A1C1">~</span><span style="color: #D8DEE9FF">/.ssh/aur -C </span><span style="color: #ECEFF4">"</span><span style="color: #A3BE8C">GH AUR - your-email@example.com</span><span style="color: #ECEFF4">"</span></div></code></div></pre>
</li>
<li>
<p>Add the public key to your AUR account:</p>
<ul>
<li>Go to <a href="https://aur.archlinux.org/">https://aur.archlinux.org</a> → My Account → SSH Public Keys</li>
<li>Paste contents of <code>~/.ssh/aur.pub</code></li>
</ul>
</li>
<li>
<p>Add the private key as a GitHub secret:</p>
<ul>
<li>Go to your repo → Settings → Secrets and variables → Actions</li>
<li>Create secret named <code>AUR_SSH_PRIVATE_KEY</code></li>
<li>Paste contents of <code>~/.ssh/aur</code></li>
</ul>
</li>
</ol>
<p>Then create a GitHub Actions workflow that you can run. You can't add it to your <code>npm release</code> workflow as there is a delay between publishing and it appearing in the API.</p>
<p>There's some tricky <code>runuser</code> logic required when updating <code>.SRCINFO</code> as you can't run <code>makepkg</code> as <code>root</code>:</p>
<pre class="shiki nord" style="background-color: #2e3440ff; color: #d8dee9ff"><div class="language-id">yaml</div><div class="code-container"><code><div class="line"><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Update AUR Package</span></div><div class="line"></div><div class="line"><span style="color: #81A1C1">on</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">workflow_dispatch</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">schedule</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">cron</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">"</span><span style="color: #A3BE8C">0 0 * * 0</span><span style="color: #ECEFF4">"</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88"># Weekly on Sunday at midnight UTC</span></div><div class="line"></div><div class="line"><span style="color: #8FBCBB">jobs</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">update-aur</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">runs-on</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">ubuntu-latest</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">container</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">image</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">archlinux:base-devel</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">steps</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Install dependencies</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">pacman -Sy --noconfirm jq openssh git</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Get latest npm version</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">id</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">npm</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></div><div class="line"><span style="color: #A3BE8C"> VERSION=$(curl -s https://registry.npmjs.org/trello-cli/latest | jq -r '.version')</span></div><div class="line"><span style="color: #A3BE8C"> echo "version=$VERSION" >> $GITHUB_OUTPUT</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Download tarball and get sha512sum</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">id</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">checksum</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></div><div class="line"><span style="color: #A3BE8C"> curl -sLO "https://registry.npmjs.org/trello-cli/-/trello-cli-$.tgz"</span></div><div class="line"><span style="color: #A3BE8C"> SHA512=$(sha512sum trello-cli-$.tgz | cut -d' ' -f1)</span></div><div class="line"><span style="color: #A3BE8C"> echo "sha512=$SHA512" >> $GITHUB_OUTPUT</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Setup SSH for AUR</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></div><div class="line"><span style="color: #A3BE8C"> mkdir -p ~/.ssh</span></div><div class="line"><span style="color: #A3BE8C"> echo "$" > ~/.ssh/aur</span></div><div class="line"><span style="color: #A3BE8C"> chmod 600 ~/.ssh/aur</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Clone AUR repo</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">env</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GIT_SSH_COMMAND</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">"</span><span style="color: #A3BE8C">ssh -i ~/.ssh/aur -o IdentitiesOnly=yes -o StrictHostKeyChecking=no</span><span style="color: #ECEFF4">"</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">git clone ssh://aur@aur.archlinux.org/trello-cli.git aur-repo</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Check if update needed</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">id</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">check</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">working-directory</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">aur-repo</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></div><div class="line"><span style="color: #A3BE8C"> CURRENT=$(grep '^pkgver=' PKGBUILD | cut -d= -f2)</span></div><div class="line"><span style="color: #A3BE8C"> if [ "$CURRENT" = "$" ]; then</span></div><div class="line"><span style="color: #A3BE8C"> echo "Already up-to-date (version $CURRENT)"</span></div><div class="line"><span style="color: #A3BE8C"> echo "skip=true" >> $GITHUB_OUTPUT</span></div><div class="line"><span style="color: #A3BE8C"> else</span></div><div class="line"><span style="color: #A3BE8C"> echo "Update available: $CURRENT -> $"</span></div><div class="line"><span style="color: #A3BE8C"> echo "skip=false" >> $GITHUB_OUTPUT</span></div><div class="line"><span style="color: #A3BE8C"> fi</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Update PKGBUILD</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">steps.check.outputs.skip != 'true'</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">working-directory</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">aur-repo</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></div><div class="line"><span style="color: #A3BE8C"> sed -i "s/^pkgver=.*/pkgver=${{ steps.npm.outputs.version }}/" PKGBUILD</span></div><div class="line"><span style="color: #A3BE8C"> sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD</span></div><div class="line"><span style="color: #A3BE8C"> sed -i "s/^sha512sums=.*/sha512sums=('${{ steps.checksum.outputs.sha512 }}')/" PKGBUILD</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Update .SRCINFO</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">steps.check.outputs.skip != 'true'</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">working-directory</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">aur-repo</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></div><div class="line"><span style="color: #A3BE8C"> useradd -m builder</span></div><div class="line"><span style="color: #A3BE8C"> mkdir -p /home/builder/build</span></div><div class="line"><span style="color: #A3BE8C"> cp -a . /home/builder/build</span></div><div class="line"><span style="color: #A3BE8C"> chown -R builder:builder /home/builder/build</span></div><div class="line"><span style="color: #A3BE8C"> runuser -u builder -- bash -lc 'cd /home/builder/build && makepkg --printsrcinfo > .SRCINFO'</span></div><div class="line"><span style="color: #A3BE8C"> cp /home/builder/build/.SRCINFO ./.SRCINFO</span></div><div class="line"></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">Commit and push</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">if</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">steps.check.outputs.skip != 'true'</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">working-directory</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">aur-repo</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">env</span><span style="color: #ECEFF4">:</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GIT_SSH_COMMAND</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">"</span><span style="color: #A3BE8C">ssh -i ~/.ssh/aur -o IdentitiesOnly=yes -o StrictHostKeyChecking=no</span><span style="color: #ECEFF4">"</span></div><div class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">run</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></div><div class="line"><span style="color: #A3BE8C"> git config user.name "Michael Heap"</span></div><div class="line"><span style="color: #A3BE8C"> git config user.email "m@michaelheap.com"</span></div><div class="line"><span style="color: #A3BE8C"> git add PKGBUILD .SRCINFO</span></div><div class="line"><span style="color: #A3BE8C"> git commit -m "Update to version $"</span></div><div class="line"><span style="color: #A3BE8C"> git push</span></div></code></div></pre>
<p>Now you have a workflow that will update your <code>PKGBUILD</code> automatically once per week, or whenever you trigger the action manually.</p>
Yes, if…2026-02-04T20:25:58Zhttps://michaelheap.com/yes-if/<p>People love hearing “yes.”</p>
<p>In a meeting, it sounds like momentum. In Slack, it reads as alignment. On a roadmap, it feels like progress.</p>
<p>It's also the first step towards disappointment. "Yes" is a contract - a commitment to deliver something. If you say "yes" to everything, you start paying for it with late nights, dropped balls and a creeping resentment that's hard to admit because you <em>chose</em> to say yes.</p>
<p>Every “yes” goes on an invisible ledger. Time, attention, credibility. All quietly debited, rarely reconciled. Eventually you <em>will</em> go bankrupt and disappoint someone. The only question is whether you disappoint them when they ask, or later when your lack of delivery has a much higher cost.</p>
<h2 id="the-two-types-of-%22yes%22" tabindex="-1">The two types of "yes"</h2>
<p>We say “yes” for many reasons. In personal relationships, it can strengthen bonds and make others feel valued. In professional settings, saying “yes” can signal teamwork, ambition, and a commitment to shared goals. However, saying “yes” can also be driven by <em>fear</em>. A fear of disappointing others, a fear of missing out, or a fear of conflict.</p>
<p>No matter <em>why</em> we say yes, the expectation to be agreeable leads to us committing to way more than we can ever hope to achieve. You say yes to avoid disappointing someone, only to disappoint them at the worst possible time: after you've agreed, they've told other people about their idea and half of the org is excited.</p>
<p>So, how do you keep the momentum that "yes" brings without overcommitting yourself? By using "Yes, if."</p>
<h2 id="%22yes%2C-if%22-is-a-trade" tabindex="-1">"Yes, If" is a trade</h2>
<p>“Yes” pretends there is no conversation. It hides the cost and defers the pain.</p>
<p>“No” without context shuts the conversation down before anything useful is learned.</p>
<p>“Yes, if” <em>is</em> the conversation.</p>
<p>“Yes, if” makes the cost visible. It takes the invisible tax you were about to pay and puts it on the table where everyone can see it. Not as a complaint. Not as drama. As accounting.</p>
<ul>
<li>“Yes, if you can wait until Thursday after the report goes out.”</li>
<li>“Yes, if we agree this replaces the other thing I’m doing.”</li>
<li>“Yes, if you do the first draft and I’ll review.”</li>
<li>“Yes, if we can fund the work and assign an owner.”</li>
</ul>
<p>Most frustration at work doesn’t come from disagreement. It comes from ambiguity.</p>
<h2 id="applying-this-in-real-life" tabindex="-1">Applying this in real life</h2>
<p>A good “Yes, if” is just good bookkeeping. It names which account you’re drawing from.</p>
<ul>
<li>Time: “Yes, if it can wait until Thursday.”</li>
<li>Priority: “Yes, if Bob agrees this outranks the report.”</li>
<li>Ownership: “Yes, if you draft it and I’ll review.”</li>
<li>Scope: “Yes, if we do a 1-page version, not a full report.”</li>
<li>Resourcing: “Yes, if we fund it / staff it.”</li>
</ul>
<p>Most org pain comes from pretending those currencies are infinite. By explicitly naming them and negotiating with the requester you can be collaborative without overcommitting.</p>
<h2 id="price-the-work" tabindex="-1">Price the work</h2>
<p>“Yes, if” is about maintaining balance. It’s about making sure the work you agree to aligns with your time, energy, and priorities.</p>
<p>Next time someone asks you for something, name the trade-offs. Make the constraint explicit. Price the work. See if they still want it.</p>
<p>If that trade kills the request, it wasn’t a priority. It was just an idea looking for someone else to carry the cost.</p>
<p>“Yes, if” is about maintaining balance. It's about making sure that projects align with your time, energy, and priorities. It fosters collaboration without compromising your well-being. Next time you’re faced with a request, remember: you don’t have to say “yes” to everything, but with the right conditions in place, you might just find a way to make it work for everyone.</p>
Delivery and Dopamine2026-01-28T20:08:57Zhttps://michaelheap.com/delivery-and-dopamine/<p>I've built my career around being the person that delivers. Throw me into a messy problem and I'll figure it out and make progress.</p>
<p>My performance reviews praised output. Peers trusted me as the person who “got things done”. The system rewarded shipping, and for a long time I used that as my definition of “doing my job well”. I confused visibility with value, and if I’m being totally honest, I was addicted to being the person that everyone pinged.</p>
<p>Then one day, my career stalled. I just couldn't justify another promotion on output alone. Shipping things feels good in a way that designing systems doesn't. Shipping provided that immediate dopamine hit, but system design doesn't work like that. Feedback is delayed and ambiguous, and honestly, things usually get worse before they get better.</p>
<p>You can justify being a player-coach as a manager, senior manager and in some places, even a director. But at some point you need to accept that the best use of your time is to act as a multiplier for the team that you're supporting.</p>
<p>That's where I failed.</p>
<h2 id="dopamine-vs.-leverage" tabindex="-1">Dopamine vs. Leverage</h2>
<p>Execution gives you fast feedback. System design demands patience.</p>
<p>When I say “designing systems,” I don’t mean architecture diagrams. I mean the interfaces that shape how the business operates: who makes decisions, how work gets planned and what happens when things break.</p>
<p>Discipline means letting the system converge on an idea and make decisions without you "helping". It means allowing systems to fail without heroics, enabling the organization to understand the flaws in the system. It means trading that dopamine hit for organizational leverage.</p>
<p>It also means being comfortable with feeling like a failure. Letting projects fail felt irresponsible. Not being hands on felt lazy. And if I'm honest, not getting that dopamine hit felt like withdrawal.</p>
<h2 id="the-relapse" tabindex="-1">The Relapse</h2>
<p>When leaders step back, things slow down. Metrics wobble. People complain.</p>
<p>That's the system revealing itself. Heroics are praised, and organizations choose short-term relief over long-term leverage. So when things got messy, I didn’t <em>choose</em> execution, I relapsed into it. It was the easy choice. The safe choice. It was also the wrong choice.</p>
<p>I fell back into being an IC multiple times. Each time the dopamine hit was even bigger. "Things were really going downhill and you turned it around!" Each time, it was even harder to crawl back out of the routine of shipping and start thinking about the overall system.</p>
<p>In one example, there was a last minute tooling update required for a key customer. So I dove in, made the changes, got the relevant approval and shipped a fix within 24 hours. The customer was happy, the account team was happy, the engineering team appreciated not being interrupted... then less than a week later the <em>exact</em> same thing happened again. Everyone's first instinct was to message me. My save the previous week didn’t fix the problem; it taught the organization the workaround.</p>
<h2 id="the-%22helping%22-trap" tabindex="-1">The "helping" trap</h2>
<p>I told myself I was "just helping".</p>
<p>But “helping” was a trap. It kept me focused on solving the immediate problem instead of asking why this problem kept showing up. It made me the bottleneck instead of fixing the decision boundary. It let me feel needed, and in the process <strong>I stole learning from the organization</strong>. The team didn’t get stronger; the system didn’t improve; I just became the workaround.</p>
<p>I'm not <em>entirely</em> at fault, though. While I was chasing the next hit I was also operating in an environment that was easy to understand. Do good work, get rewarded. Designing systems is filled with ambiguity and risk. System design is political, cross-functional and uncertain. It's also invisible to your stakeholders (until it fails). Execution is visible, measurable and safe.</p>
<h2 id="build-a-system-that-ships-without-you" tabindex="-1">Build a System That Ships Without You</h2>
<p>Building a sustainable system is thankless work. The feedback loop is slow, and the wins are quiet. You don’t measure it by what <em>you</em> shipped - you measure it by what happens when you’re not there.</p>
<p>A system is getting healthier when:</p>
<ul>
<li><strong>Recurring fires disappear</strong> because you removed root causes instead of treating symptoms.</li>
<li><strong>Decisions happen without you</strong> (and they’re good enough. Not <em>perfect</em>. Good enough).</li>
<li><strong>Approval paths are explicit</strong>: who decides, what “good” looks like, and how long each step should take.</li>
<li><strong>Leading indicators show up early</strong>, so problems surface while they’re still cheap to fix.</li>
</ul>
<p>If you're looking for a concrete example, we moved approvals from an ad-hoc Slack-based approach with emoji reactions to a weekly forum with named decision owners and SLAs. It <em>felt</em> like more process, but the overall time investment was much lower.</p>
<p>To understand if the system is working, ask yourself "if I take a month off, does the team still make good decisions, ship on time and recover from setbacks?".</p>
<p>(Fun fact! This is one of the reasons I quit my last job. I took 3 months off when my baby was born and when I got back to work it was as though I'd been there the whole time. They no longer needed me, and it was time to go and build systems somewhere else.)</p>
<h2 id="the-organization-trained-me" tabindex="-1">The organization trained me</h2>
<p>The org rewarded heroics, and I cashed the checks. Repeatedly.</p>
<p>In many cases a leader is doing what the organization expects of them. It's not that they can't let go of execution, it's that no-one tells them that they should. So we keep delivering, getting that dopamine hit and feeling effective.</p>
<p>Until organizations realize that leadership is about designing systems that enable the work, rather than doing it, they'll keep celebrating productivity and wondering why nothing scales.</p>
<p>We have to fix the incentives. Evaluate leaders based on team throughput and decision quality, not individual output. Celebrate recurring fires that no longer happen. Put “eliminated dependencies” and “decisions made without me” into performance reviews. Heroic saves should result in a postmortem and a system fix, not a medal.</p>
<p>If heroics are the path to praise, leaders will keep relapsing (even when they know better). If you keep rewarding people for <em>being the system</em> rather than <em>building the system</em> you won’t get leaders.</p>
<p>You’ll get bottlenecks with better titles.</p>
Fix the inputs, not the outputs2026-01-21T09:58:37Zhttps://michaelheap.com/fix-the-inputs/<p>A friend of mine is a design leader at a company with a couple of thousand people. We were recently chatting about the amount of time she was spending reviewing her team's work. It wasn't because the work was bad, but because she had no idea if the design met the needs of the project. She was spending hours trying to reverse-engineer the requirements from the final design.</p>
<p>The reviews didn't feel good for the individual designers either. Their manager was coming in at the end of a project and scrutinizing every little decision without the full context of what the requirements were. As it turned out, the designers <em>also</em> didn't have the full context - they knew how it started, but not how the requirements evolved over time. That information lived somewhere between the manager, the designers, and the project lead, with no clear source of truth.</p>
<p>My friend works in a design team, but I've seen this play out in a variety of settings. Engineers without clear product requirements. DevRel teams without clear metrics. Salespeople without a solid ideal customer profile. Anytime someone's required to fill in the blanks themselves, there's a high chance that it will lead to frustration for everyone involved.</p>
<h2 id="garbage-in%2C-garbage-out" tabindex="-1">Garbage In, Garbage Out</h2>
<p>Every output is an echo of its inputs. If the team doesn't know what "good" looks like from the start, they have to make decisions that they're not informed enough to make. Sometimes it works out, but more often than not it results in conflict.</p>
<p>The person reviewing the output feels that something is "off", while the team is blindsided by feedback that could have been provided as a requirement early, before the work was done.</p>
<p>Good requirements aren't about more documentation - they're about building a shared understanding of what you're trying to achieve.</p>
<ul>
<li>What problem are we solving?</li>
<li>What does success look like?</li>
<li>What constraints do we have?</li>
</ul>
<p>By aligning on the answer to these questions early, you make it easy to evaluate the work objectively. Did it solve the problem within the constraints expressed? Yes? Excellent work! Let's ship it.</p>
<h2 id="a-stitch-in-time" tabindex="-1">A Stitch in Time</h2>
<p>"But we don't have time to do such rigorous alignment work! They need this delivered yesterday!" I hear you, I understand, and respectfully, I disagree.</p>
<p>You're already spending this time, and more, at the end of the project "catching problems" with the implementation. It feels productive to catch the issues, but doing it after the work is complete is both expensive and demoralizing for the team.</p>
<p>Aligning on the inputs takes time, but it takes less time than trying to reconstruct requirements from an output. It takes less time than redoing the work because someone made a decision they didn't have enough context to make. Investing the time up front pays off in the long run. <em>A stitch in time saves nine.</em></p>
<blockquote>
<p>In order to improve for good, you need to solve problems at the systems level. Fix the inputs and the outputs will fix themselves. - <strong><em>James Clear, Atomic Habits</em></strong></p>
</blockquote>
<h2 id="how-to-fix-the-inputs" tabindex="-1">How to Fix the Inputs</h2>
<p>Fixing the inputs isn't hard, but it does take some concerted effort. At your next project kickoff, try asking the initial framing questions:</p>
<ul>
<li>What problem are we solving?</li>
<li>What does success look like?</li>
<li>What constraints do we have?</li>
</ul>
<p>Take the answers provided and write them down in a location that can be linked to from the work you do. This provides the base for your requirements doc.</p>
<p>Over time, you'll build up a list of additional questions that are specific to your org, for example:</p>
<ul>
<li>Is this intended for new users or for experienced users?</li>
<li>Are they likely to interact on a small screen, or will they always be on a laptop-sized screen?</li>
<li>There's a lot of data being processed here. Are users expecting a result within a second, a minute or an hour?</li>
</ul>
<p>Of course, not every requirement can be known in advance. Discovery happens through iteration and exploration. You learn what users really need, what's technically feasible, and what trade-offs actually matter by trying things out.</p>
<p>That's normal. The problem isn't that requirements evolve, it's that the written requirements are rarely updated as the real world requirements change. The initial assumptions are treated as the "requirements" even as reality shifts. This is even worse than having no requirements at all, as the final output is then evaluated against incorrect assumptions.</p>
<p>When requirements change, don't delete the old requirements. Instead, append to the document and make a note of the date. Then add a "current requirements" section to the top that is a synthesis of everything you know from the initial definition and the updated requirements. This is your evaluation criteria once the project is ready for review.</p>
<h2 id="the-real-definition-of-%22done%22" tabindex="-1">The Real Definition of "Done"</h2>
<p>Everyone has a different definition of "done" depending on their context and point of view. When teams say a project is "done", they mean that the solution is ready for review. In reality, the work is only done when everyone is aligned that this is the best possible outcome given the constraints.</p>
<p>Before calling something complete, review everyone's understanding of the problem and the proposed solution. Ask "Do we still agree on what good looks like? Are the inputs still valid?"</p>
<p>When inputs are solid, reviews become easier. Feedback stops becoming a surprise attack and becomes a shared evaluation against a known goal. Teams spend less time justifying their decisions and more time improving the product. "Done" stops meaning "I hope this is what you wanted" and starts meaning "we all understand why this is the best solution given the trade-offs".</p>
<p>So the next time you're tempted to try and reverse engineer what was required from the final output (and inevitably provide misguided feedback), pause and think about what led you to this point.</p>
<p>Don't fix the output.</p>
<p>Fix the input.</p>
When Asking is Easy (and Answering Isn't)2026-01-14T08:34:05Zhttps://michaelheap.com/asymmetric-questions/<p>It's 4pm on a Thursday, and I'm patting myself on the back that I've managed to align multiple teams on a tricky project. The requirements document has been reviewed and approved by everyone involved, and I'm excited to see what's been scoped come to life.</p>
<p>I'm about to sign off for the day when someone from our SaaS team pinged me: "Hey, quick question. What would it take to make this multi-tenant?". It's as though the walls come crashing down around me. It's not a quick question at all - it's one that takes deep technical, historical, and organizational knowledge to unpack.</p>
<p>I sigh as I make another cup of tea and settle in to write an essay of a response, trying to balance the need for details without it appearing as though I'm trying to overwhelm them with complex explanations.</p>
<p>Questions like this aren't malicious. They're often great questions, asked in good faith. But they reveal something important about effort - it's much easier to ask questions than to answer them.</p>
<h2 id="asymmetric-questions" tabindex="-1">Asymmetric Questions</h2>
<blockquote>
<p>An asymmetric question is one where the effort to ask is trivial compared to the effort to answer.</p>
</blockquote>
<p>This asymmetry isn't inherently bad. I'd choose to work with a curious team over a disinterested team every time given a choice. But when left unchecked, these questions can lead to a high emotional load for those receiving them.</p>
<p>I've been the person receiving the questions, and my responses have ranged from "Why are they asking questions that they definitely won't understand the answer to?" (which was an unkind categorization on my behalf) to "This sucks. Weeks of alignment undone by a single question. Maybe I can pretend I didn't see it?" to "This is a good question. I should have covered this in the first place. It's going to take time, but it's worthwhile".</p>
<p>Lack of knowledge is often the driver of asymmetric questions. The person asking is often missing the context required to realize how much effort it will take to answer what appears to be a simple question.</p>
<p>In general, the more senior or cross-functional your audience, the higher the likelihood is of one of them asking an asymmetric question. This isn't malicious - their curiosity spans domains that they're not totally familiar with.</p>
<p>Asymmetric questions can be valuable, but they can also be distracting. If the system that the asker is operating in rewards visibility, you're going to get spontaneous questions that don't add much. They don't care as much about the answer as they do about being seen to contribute. Over time, this erodes focus and encourages reactive rather than strategic work.</p>
<h2 id="managing-the-imbalance" tabindex="-1">Managing the Imbalance</h2>
<p>Asymmetric questions aren't malicious. They're born from curiosity, but left unchecked they can command a lot of time. It takes 30 seconds to ask a question, and 30 minutes to answer it.</p>
<p>My rule of thumb for asymmetric questions is to spend twice as long answering a question as it took to ask it. If they spent a minute asking a question, I'll give it two minutes of thought then respond (usually to ask clarifying questions of my own). The asker's response might take 5 minutes to write, and so I'll take 10 minutes to respond.</p>
<p>If a question takes one minute to write and will take twenty minutes to respond to, schedule a call. That's a good sign that there's something missing from either the asker's knowledge, or from your proposal itself.</p>
<p>The goal isn't to stop people asking questions. It's to right-size your response.</p>
<h2 id="navigating-asymmetric-questions" tabindex="-1">Navigating Asymmetric Questions</h2>
<p>The first step to navigating asymmetric questions successfully is to get past the resentment stage as quickly as possible. Even being aware of the knowledge asymmetry, I still have an initial burst of resentment every time someone asks "why don't we do X?".</p>
<p>The impact that their question has on the project, and on me as an individual, is huge. But I need to separate intent from impact. People don't generally ask asymmetric questions without a reason.</p>
<p>Once you're past the initial resentment, you can engage with the asker in good faith. Many people see a question and feel like they have to answer it immediately, and in depth. This usually isn't the case.</p>
<h3 id="exploring-or-deciding" tabindex="-1">Exploring or Deciding</h3>
<p>Questions on proposals are either <em>exploring</em> or <em>deciding</em>.</p>
<p>If someone is <em>exploring</em> an idea e.g. "Did we consider internationalizing the docs?" then you can respond with a similar level of depth. You could say "We did, but the return on investment for both time and cost wasn't there. I'm happy to dig in more if you like."</p>
<p>If the same question is phrased slightly differently, they're looking for a <em>decision</em>: "Why aren't we internationalizing the docs?". My personal instinct here is to write a 10 paragraph response in the comments section of a Google doc, but that's the wrong approach. Instead, propose a follow-up with the correct audience: "That's a good question. A full answer will take some time to put together. Can we chat next week with a few others that were involved in the decision?"</p>
<h3 id="clarify-and-redirect" tabindex="-1">Clarify and Redirect</h3>
<p>If the question is open-ended, don't try to answer every potential question. Here's one question that I had recently:</p>
<blockquote>
<p>Why don't we provide a hosted version of {Dependency X}?</p>
</blockquote>
<p>Instead of diving in, I asked a clarifying question to narrow the scope: "There a couple of reasons. Are you asking about technical, cost or market reasons?". The author came back with "market" and I explained that a hosted version would be well received by the market, but the technical implications (single tenant dependency) meant that the cost of providing a service (COGS) was too high for it to be a viable option for us.</p>
<p>Asking questions turns what sounds like a demand into a conversation. Either the asker wants to really dig in and you can have a fruitful conversation, or (as I've experienced many times) they just don't respond at all and you can forget about the question entirely.</p>
<h2 id="answer-with-intention" tabindex="-1">Answer with Intention</h2>
<p>Asymmetric questions are inevitable in collaborative work, especially as scope and seniority increases. By clarifying the questions and matching your effort to their intent you can keep projects moving without letting "one quick question" derail weeks of progress.</p>
Visibility is Velocity2026-01-07T11:29:57Zhttps://michaelheap.com/visibility-is-velocity/<p>Once upon a time, I worked with a Product Manager, Bob. Bob's team worked with incoming data pipelines, and transformed and augmented a variety of disparate data sources in to a consistent data model. It was a tough job, as you didn't actually know what data you were going to receive from our providers until it arrived.</p>
<p>Bob’s team also had a reputation for being <em>slow</em>. On average, onboarding a new data source took about three months. When the work shipped, though, it was excellent: reliable, well-instrumented, and easy to maintain.</p>
<p>But when they shipped, they didn't get any accolades. The team heard "Finally! Why did it take so long?". Worse, they heard "I wish you'd have warned us - we haven't implemented the new data source in the billing system". So not only did the project <em>feel</em> late, the business couldn’t even use what had been delivered.</p>
<p>A few months later, Bob was no longer with the company. The feedback was that he “wasn’t compatible with how the business needed to operate.” We hired Alice as his replacement.</p>
<p>The perceived change was immediate. Alice shipped something in her first two weeks. People were impressed. It didn’t even seem plausible that Bob’s team could ship <em>anything</em> in two weeks, let alone within two weeks of a new PM starting.</p>
<p>But Alice didn’t <em>actually</em> make the team faster. New data sources still took three months to fully integrate. The work was the same. The complexity was the same. What changed was that progress became visible.</p>
<p>And suddenly, everyone believed the team had become fast.</p>
<h2 id="the-dangers-of-silence" tabindex="-1">The Dangers of Silence</h2>
<p>Humans hate ambiguity. When there’s no signal, people invent their own story.</p>
<p>Bob's lack of updates created its own narrative: the work had stalled, or it wasn’t a priority. The project lost momentum. Other teams postponed their related work. Leaders delayed decisions tied to the project. Everyone just kind of "forgot" about the ongoing work.</p>
<p>The lack of communication usually comes from a good place:</p>
<blockquote>
<p>“I didn’t want to distract people until I had something solid.”</p>
</blockquote>
<p>But updates aren't a distraction. Surprises are. And surprises are expensive for everyone.</p>
<p>Each and every time I've found myself surprising an executive, I've ended up on calls where they're trying to figure out what's going on. Those calls take a <em>lot</em> of time.</p>
<p>We need to re-establish context for those that aren't deep in the project, then explain what's happening and why. The conversation inevitably turns to re-litigation of a decision that was already made.</p>
<p>Before you know it, the engineering team is frustrated that we're revisiting old decisions and the execs are frustrated that things are taking even longer. Surprises don't serve anyone.</p>
<h2 id="the-power-of-updates" tabindex="-1">The Power of Updates</h2>
<p>Sending regular updates doesn't make you go any faster - in fact, it slows you down slightly as writing the updates takes time. You don't want them to be a torrent of useless information ("We added 14 new tests and added support for the FlubJam"). Instead, focus on what's useful ("We added tests to ensure that URL mapping works consistently, and integrated FlubJam so that the billing team can get started with their work").</p>
<p>These small, regular updates build trust and predictability for your audience. They know what's happening, and that the project is still on track. And if there <em>is</em> something that isn't quite right, these regular updates allows people to raise concerns, build alignment and course correct while the cost of doing so is cheap.</p>
<h2 id="how-to-ship-incrementally" tabindex="-1">How to Ship Incrementally</h2>
<p>Let's go back to Bob and Alice. Alice didn’t deliver results any faster. She just made progress visible.</p>
<p>Instead of waiting for everything to be solid, she shared updates like these:</p>
<ul>
<li>Week 2: Great news! We've connected to the third party system. Here's a video of us ingesting a piece of data (no mapping, no augmentation)</li>
<li>Week 4: We've figured out what data is available. Here's a link to our proposed data mapping, and a demo video with the mapping added</li>
<li>Week 6: The pipeline is running well, and we've managed to add some augmentations. Here's a video of URL extraction from text working. Look how it augments the data with the page title from the URL</li>
<li>Week 8: Slow progress this week. We realised that the data isn't guaranteed to arrive in order, so it turns out that we were missing some data. We're reworking our pipeline to use the deduplication service so that we don't have to rely on timestamps. I'm still optimistic that we'll be done next month</li>
<li>Week 10: All solved! I've shared the data size/volume information with the billing team so that they can load it into the rate card system. We're working on additional test cases to give us confidence that we haven't missed anything.</li>
<li>Week 12: Tada! The project is done. Here's a list of sample configurations and test cases that we run. Thank you for all the feedback along the way.</li>
</ul>
<p>We still couldn't ship the new data source to customers until week 12, but all of the stakeholders were clear what was happening every step of the way.</p>
<p>Compare that to Bob's updates:</p>
<ul>
<li>Week 12: The integration is complete. Here’s the final implementation - let me know if you have feedback.</li>
</ul>
<p>The engineering team did the same work on the same timeline, but the leadership team had a <em>very different perception</em>.</p>
<p>Alice wasn’t seen as faster because she did more. She was seen as faster because progress showed up continuously. Risks were surfaced early, course corrections happened when needed, and no one was surprised at the end.</p>
<p>Your updates don’t need to be long. Fifteen minutes to produce. Two minutes to consume. That's all it takes.</p>
Bulk unsubscribe in Gmail2026-01-06T10:23:42Zhttps://michaelheap.com/gmail-unsubscribe/<p>Back in 2021 I added an idea to <a href="https://michaelheap.com/ideas">/ideas</a> that would scan my email inbox and provide a single page containing unsubscribe links that have been scraped from emails. Here was the proposal:</p>
<blockquote>
<p>A UI that reads all emails in a folder and shows all emails with an <code>unsubscribe</code> link.<br />
Provide a way to group by sender / subject / unsubscribe URL<br />
I'd like to be able to choose which folder this runs in (minimum Inbox + Trash, but ideally any folder/label)</p>
</blockquote>
<p>Today I learned that Google have built this themselves. In your inbox, click on <code>More</code> in the sidebar and then choose <code>Manage subscriptions</code>. You'll see a screen that shows you email volume from each sender and allows you to unsubscribe with a single click.</p>
<div class="image-wrapper "><picture> <source class="" type="image/webp" srcset="https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-320.webp 320w, https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-640.webp 640w, https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-960.webp 960w, https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-1200.webp 1200w" sizes="(max-width: 320px) 320px, (max-width: 640px) 640px, (max-width: 960px) 960px, (max-width: 1200px) 1200px, 100vw" /> <img class="" alt="Gmail Manage Subscriptions page screenshot" src="https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-1200.jpeg" sizes="(max-width: 320px) 320px, (max-width: 640px) 640px, (max-width: 960px) 960px, (max-width: 1200px) 1200px, 100vw" srcset="https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-320.jpeg 320w, https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-640.jpeg 640w, https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-960.jpeg 960w, https://michaelheap.com/images/gmail-unsubscribe/manage-subscriptions.png/TLIW-f1pvP-1200.jpeg 1200w" width="1200" height="592" /> </picture></div>
<p>Now, I need to go and unsubscribe from all of these Kickstarter platform emails that I always delete without reading...</p>