<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tom Mango</title>
    <description>The latest articles on Forem by Tom Mango (@sleepingpotato).</description>
    <link>https://forem.com/sleepingpotato</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3544086%2F8d8017d1-f771-49f6-aaba-c6f36e7b4840.png</url>
      <title>Forem: Tom Mango</title>
      <link>https://forem.com/sleepingpotato</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sleepingpotato"/>
    <language>en</language>
    <item>
      <title>Quick Shell Access from Claude Code</title>
      <dc:creator>Tom Mango</dc:creator>
      <pubDate>Sun, 08 Feb 2026 20:22:30 +0000</pubDate>
      <link>https://forem.com/sleepingpotato/quick-shell-access-from-claude-code-325c</link>
      <guid>https://forem.com/sleepingpotato/quick-shell-access-from-claude-code-325c</guid>
      <description>&lt;p&gt;When using &lt;a href="https://claude.com/product/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, you'll often want to drop to the shell for quick commands like checking git status, switching branches, or running tests. There are two main ways to do this, and which one you use depends on what you need.&lt;/p&gt;

&lt;p&gt;For single commands, Claude Code supports the &lt;code&gt;!&lt;/code&gt; prefix. Type &lt;code&gt;!git status&lt;/code&gt; or &lt;code&gt;!python -m pytest&lt;/code&gt; and it runs the command inline without leaving your session. This works well for one-off checks, but each command needs the &lt;code&gt;!&lt;/code&gt; prefix, and there's no way to stay in bash mode. If you're moving quickly through multiple commands, having to remember the prefix each time fights against muscle memory.&lt;/p&gt;

&lt;p&gt;For more involved shell work, you can suspend Claude Code entirely with &lt;code&gt;ctrl+z&lt;/code&gt;, which gives you your full shell prompt. Here, you can handle tasks like git branch management yourself instead of asking Claude to do them and burning tokens and network round-trips for something trivial. When you're done, run &lt;code&gt;fg&lt;/code&gt; to resume Claude Code exactly where you left off.&lt;/p&gt;

&lt;p&gt;One caveat: only suspend when Claude is idle and waiting for your next message, not during an active generation. When you hit &lt;code&gt;ctrl+z&lt;/code&gt;, you're suspending the local client process. If Claude is generating a response, the server-side work continues while your client is frozen. When you &lt;code&gt;fg&lt;/code&gt; back, the client and server can be out of sync, and you risk losing the response or corrupting the session state.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>cli</category>
    </item>
    <item>
      <title>Product Engineering &amp; Pragmatism</title>
      <dc:creator>Tom Mango</dc:creator>
      <pubDate>Sat, 03 Jan 2026 16:26:36 +0000</pubDate>
      <link>https://forem.com/sleepingpotato/product-engineering-pragmatism-3kki</link>
      <guid>https://forem.com/sleepingpotato/product-engineering-pragmatism-3kki</guid>
      <description>&lt;p&gt;Let's get the caveat out of the way first. My perspective here is about businesses. There are certainly different tradeoffs and values in other types of organizations, but I'm talking specifically about businesses that employ software engineers to build software that customers pay for.&lt;/p&gt;

&lt;p&gt;Even within businesses there are sliding scales on tradeoffs, but typically, time spent by people costs money. What engineers spend their time on is an investment, and investments are meant to have a return.&lt;/p&gt;

&lt;h2&gt;
  
  
  Healthy Organizations
&lt;/h2&gt;

&lt;p&gt;Healthy organizations are pragmatic. Not just on the engineering side, but on the business side as well. Ideally, they don't even refer to themselves as "sides" of the company. That language implies you're working against each other, not together. But it happens a lot.&lt;/p&gt;

&lt;p&gt;There are a lot of idealistic engineers out there. I think it's good to be idealistic, but only in places where you have time that doesn't directly cost money, and that is almost certainly not at your job. Being pragmatic means understanding what tradeoffs exist and how to make them. It also means being idealistic on your own time, where you can explore ideas, experiment, and hone your skills without those constraints.&lt;/p&gt;

&lt;p&gt;Idealistic engineers who understand when to be idealistic and when to be pragmatic tend to become champions of things like strong culture. They rally their teams. They know how to balance progress with technical debt. They find compromises. They put data together to convince partners outside of engineering why certain investments are worth making. Over time, they build trust and bargaining power because leadership sees that they understand the broader context. Trust is powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Idealism Breaks Down
&lt;/h2&gt;

&lt;p&gt;The problem is when idealistic engineers can't come to terms with pragmatism in a business environment. When there isn't unlimited time, because time costs money. In my experience, those engineers don't become champions. They don't rally the team. When they don't get their ideal outcome, frustration turns into negativity, and that negativity seeps into everything. It drains the joy out of the work. Negativity kills teams.&lt;/p&gt;

&lt;p&gt;There are, of course, roles inside businesses where people can take more time, go deeper, or move at their own pace. But realistically, businesses paying those salaries aren't doing it out of the goodness of their hearts. They're still betting on a return. Open source investments fall into this category, and that isn't a bad thing. Businesses investing in open source can be a win-win. It's good for the company and good for others. But it still needs to be good for the company first.&lt;/p&gt;

&lt;p&gt;I've worked with idealistic engineers who were incredibly smart but not pragmatic at all. They often felt “other than” the rest of the organization. Some of them looked down on the product engineers who were focused on supporting and shipping the product. Once that happens, the team stops listening to their advice. But add a dose of pragmatism, and you get someone who is still pushing standards, while also meeting the team where they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culture
&lt;/h2&gt;

&lt;p&gt;Product engineering is what most employed software engineers in businesses are hired to do, and successful product engineers are pragmatic. I'll work with a pragmatic product engineer any day of the week. But an idealistic product engineer who is also pragmatic tends to be the kind of person teams rally around. They work with partners outside of engineering to deliver what needs to get delivered, while also working with their engineering peers to do those things well.&lt;/p&gt;

&lt;p&gt;For as long as I can remember, I've told my teams that everything is a negotiation. We're there to deliver what's prioritized, but it's also our responsibility to speak up and make the case for the tradeoffs we think matter.&lt;/p&gt;

&lt;p&gt;So by all means, be idealistic. Just make sure you can also be pragmatic, where and when it counts.&lt;/p&gt;

</description>
      <category>software</category>
      <category>management</category>
    </item>
    <item>
      <title>Stacking PRs &amp; Squashing Merges</title>
      <dc:creator>Tom Mango</dc:creator>
      <pubDate>Wed, 08 Oct 2025 20:37:07 +0000</pubDate>
      <link>https://forem.com/sleepingpotato/stacking-prs-squashing-merges-4ogg</link>
      <guid>https://forem.com/sleepingpotato/stacking-prs-squashing-merges-4ogg</guid>
      <description>&lt;p&gt;Sometimes you're working on a feature that's too big for one PR and splitting it up makes it easier to review. The way I handle this is by creating a &lt;code&gt;first&lt;/code&gt; feature branch, then branch &lt;code&gt;second&lt;/code&gt; off of it, then &lt;code&gt;third&lt;/code&gt; off of &lt;code&gt;second&lt;/code&gt;. As each branch is ready for review, I open a PR and each subsequent PR stacks on the last, so reviewers can focus on one layer at a time.&lt;/p&gt;

&lt;p&gt;That setup works really well, but it gets messy when your team uses a &lt;a href="https://git-scm.com/docs/git-merge?ref=sleepingpotato.com#Documentation/git-merge.txt---squash" rel="noopener noreferrer"&gt;squash merge&lt;/a&gt; strategy where each PR turns into a single commit when merged into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's what happens: when you squash-merge &lt;code&gt;first&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;, Git collapses all of those commits into a single new commit. This is great for keeping &lt;code&gt;main&lt;/code&gt;'s history clean and readable. But your &lt;code&gt;second&lt;/code&gt; and &lt;code&gt;third&lt;/code&gt; branches are still branched off the old &lt;code&gt;first&lt;/code&gt;—the one with all those individual commits. When they compare against the new &lt;code&gt;main&lt;/code&gt;, they'll suddenly include all of &lt;code&gt;first&lt;/code&gt;'s commits in their diff. The diff becomes noisy and reviewers can't tell what's actually new in &lt;code&gt;second&lt;/code&gt; or &lt;code&gt;third&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix is to rebase your next branch back onto &lt;code&gt;main&lt;/code&gt;. When you do that with the right flags, Git will replay just the commits unique to that branch on top of the new history, dropping anything that already exists upstream. Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Rebase the next branch (second) onto main&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git fetch origin
git checkout feature/second
git rebase --onto origin/main origin/feature/first
git push --force-with-lease
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--onto&lt;/code&gt; flag tells Git: "replay the commits from &lt;code&gt;second&lt;/code&gt; that don't exist in &lt;code&gt;first&lt;/code&gt;, putting them on top of &lt;code&gt;main&lt;/code&gt; instead." Then update the &lt;code&gt;second&lt;/code&gt; PR's target branch to &lt;code&gt;main&lt;/code&gt;. Now when reviewers look at the PR, they'll see only the changes &lt;code&gt;second&lt;/code&gt; introduced—no noise from the merged &lt;code&gt;first&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Rebase deeper branches (third) with --fork-point&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once you've rebased &lt;code&gt;second&lt;/code&gt;, its commit history changes. If &lt;code&gt;third&lt;/code&gt; was branched from the old version of &lt;code&gt;second&lt;/code&gt;, it's still holding onto all of &lt;code&gt;first&lt;/code&gt; and &lt;code&gt;second&lt;/code&gt;'s commits. You can't just use &lt;code&gt;--onto&lt;/code&gt; again because you need Git to figure out where &lt;code&gt;third&lt;/code&gt; originally forked from the old &lt;code&gt;second&lt;/code&gt;, even though those commit hashes no longer exist. That's where &lt;code&gt;--fork-point&lt;/code&gt; comes in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git fetch origin
git checkout feature/third
git rebase --onto origin/main --fork-point origin/feature/second
git push --force-with-lease
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--fork-point&lt;/code&gt; flag does the heavy lifting: it finds the point where &lt;code&gt;third&lt;/code&gt; originally branched from &lt;code&gt;second&lt;/code&gt;, traces through the rebasing that happened to &lt;code&gt;second&lt;/code&gt;, and figures out which commits in &lt;code&gt;third&lt;/code&gt; are actually new. Then it replays just those commits on top of &lt;code&gt;main&lt;/code&gt;. The result is what you want—&lt;code&gt;third&lt;/code&gt; now shows only the changes it introduced, not everything from &lt;code&gt;first&lt;/code&gt; and &lt;code&gt;second&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why this matters&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The real value here is keeping each PR's diff clean and focused. When reviewers look at a stacked PR, they can actually see what that specific branch contributed without wading through noise from previous merges. It's a small bit of bookkeeping, but it makes the difference between a review that's clear and one that's confusing.&lt;/p&gt;

&lt;p&gt;The technique is straightforward, but it's worth understanding what's actually happening. A squash-merge creates a new commit that replaces an entire branch's history. Your stacked branches are still pointing at the old history. Rebasing with the right flags replays your branch's work on top of the new baseline, so everything stays clean and readable. It's one of those moments where knowing how Git thinks about commits—not branches or PRs, but commits—suddenly makes a workflow problem disappear.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>development</category>
    </item>
    <item>
      <title>Platform Engineering: Easy to Use, Hard to Mess Up</title>
      <dc:creator>Tom Mango</dc:creator>
      <pubDate>Fri, 03 Oct 2025 18:33:09 +0000</pubDate>
      <link>https://forem.com/sleepingpotato/platform-engineering-easy-to-use-hard-to-mess-up-471o</link>
      <guid>https://forem.com/sleepingpotato/platform-engineering-easy-to-use-hard-to-mess-up-471o</guid>
      <description>&lt;p&gt;We used to have a saying on Recharge's Platform Services team: &lt;em&gt;It should be really easy to use and really hard to fuck up&lt;/em&gt;. As a team building a platform that allowed not only ourselves, but other engineering teams to quickly spin up services, it's easy to see why we said this so often.&lt;/p&gt;

&lt;p&gt;This team didn't really start as a Capital-P "Platform" team in the general sense, though. It started as a team that built an eventing and serverless platform (the Event Bus), then built first-party services on top, and exposed those services for other teams to consume. After each service we built on the Event Bus, we updated the underlying patterns, we defined new golden paths, we improved observability, and we exposed more utilities (like API clients, storage abstractions, base Cloud Function classes, etc). These services were so successful (in terms of launching quickly, enabling quick iteration, and scaling fast) that other teams wanted to start building services on the Event Bus too.&lt;/p&gt;

&lt;p&gt;At first, other teams would look at what was done previously, recreate the setup of a repo, and go forward. This worked great early on. Other teams were getting a lot of the benefits. A lot, but not all. Many guardrails to keep us on the golden paths were conventions based on internal team knowledge. Also, our team didn't stop evolving the Event Bus platform. So, drift set in between repos. And once drift sets in, it compounds.&lt;/p&gt;

&lt;p&gt;If you're having to set up CI/CD, API clients, error handling, and logging with each new service repo, you will almost certainly have drift between service repos. This means if someone moves from one service repo to another, there are nuances that will inevitably bite them. This also goes for incident response. The more "sameness" between services, the smaller context people need to know to solve things. They aren't looking in different places for different things. They know what they're looking for because it's the same for every service.&lt;/p&gt;

&lt;p&gt;You see, there's a difference between having a single team that owns their own platform and a Platform that is set up in a way to enable other teams. We had to evolve into a Capital-P "Platform" team.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Mean by Platform Engineering
&lt;/h2&gt;

&lt;p&gt;I tend to think of Platform Engineering as building internal platforms for the delivery and lifecycle management of services. It's about codifying the golden paths and making the right way, the easy way. Or, &lt;em&gt;making things really easy to use and really hard to fuck up&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But, Platform Engineering is a multiplier. It amplifies whatever patterns and practices you already have. If your service design patterns are solid, Platform Engineering makes them consistently excellent across all teams. If your patterns are poor, Platform Engineering just makes it easier to build bad services faster.&lt;/p&gt;

&lt;p&gt;In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardization&lt;/strong&gt; – Every service starts from the same baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Service&lt;/strong&gt; – Spinning up a new service takes minutes, not weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails, Not Gates&lt;/strong&gt; – You can move fast, but you stay safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enablement Over Enforcement&lt;/strong&gt; – Developers spend their time on product logic, not boilerplate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ongoing Updates&lt;/strong&gt; – Each service can easily be kept updated to get the latest improvements shared across the platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without Platform Engineering, you're reinventing CI/CD, IAM, observability, and more for every new service. As anyone who's ever set up a CI/CD pipeline knows, even when you have a reference point, it takes iteration to get it right. The same goes for API clients, error handling and logging layers, normalized request/response structures. All of those are decisions and configuration that take time to set up and get working correctly. With Platform Engineering, you run something like &lt;code&gt;service new object-cache&lt;/code&gt; and start writing business logic immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Options
&lt;/h2&gt;

&lt;p&gt;What happened with the Platform Services team was not only typical, but healthy. It's how pragmatic teams in pragmatic engineering organizations evolve. Like that team, most teams I’ve seen in this situation face the same three choices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Do nothing&lt;/strong&gt; – Keep shipping, share one-off improvements between service repos, and fix problems as they come. Short-term velocity, long-term drag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporary Monorepo&lt;/strong&gt; – Pull everything together into a single monorepo, but organize features into macro services. Then, after things are standardized and well-defined, break them back out later with your Platform Engineering approach. A structured path, but risky if your monorepo isn't working well (more on this later).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eventual Consistency&lt;/strong&gt; – Keep repos separate, but invest in your Platform Engineering approach iteratively to make them eventually consistent: templates, shared modules, automation. This allows for an incremental approach but requires discipline.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Spectrum
&lt;/h2&gt;

&lt;p&gt;Whether you want to go with a temporary monorepo or stick with multiple service repos, you still need to put in the work. Both monorepo and multi-repo approaches can work, but they can both fail too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bad monorepo: slow CI, flaky tests, poor abstractions.&lt;/li&gt;
&lt;li&gt;A good monorepo: fast pipelines, great patterns, consistent experience.&lt;/li&gt;
&lt;li&gt;A bad multi-repo setup: drift and duplication.&lt;/li&gt;
&lt;li&gt;A good multi-repo setup: standardized templates, automated propagation of improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference? Good design patterns as the foundation. A bad monorepo amplifies poor abstractions and flaky patterns across all services, while a good one amplifies solid patterns consistently.&lt;/p&gt;

&lt;p&gt;We saw this drift firsthand when someone created a function handler without using the existing base class and reimplemented a number of things incorrectly. This was found during an incident call and took time to understand. We went back and aligned it with the platform and made it harder to fuck up in the future with generated templates.&lt;/p&gt;

&lt;p&gt;When a team spun up a service, we would take their questions and confusions, solve for them, then bring back improvements to the platform to benefit future services. This feedback loop is what turns either organizational approach from messy to manageable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Question
&lt;/h2&gt;

&lt;p&gt;The question isn't whether to invest in Platform Engineering. It's how to get there. Consolidate first, or align incrementally? The answer depends on your commitment, team size, growth rate, and tolerance for disruption.&lt;/p&gt;

&lt;p&gt;That said, a Platform Engineering approach isn't always the right answer. If your team is small enough that everyone can maintain shared context about patterns and practices, and you're not seeing people struggle with inconsistent implementations or step on each other during development, you might not need the formal separation and standardization yet.&lt;/p&gt;

&lt;p&gt;More importantly, if you don't have solid service design patterns established, Platform Engineering might not be your first priority. You need that foundation first – otherwise you're just making it easier to consistently build the wrong thing.&lt;/p&gt;

&lt;p&gt;With a sufficiently large organization, the Platform Engineering destination is the same: paved roads, golden paths, and engineers focused on building products instead of plumbing.&lt;/p&gt;

</description>
      <category>platformengineering</category>
      <category>management</category>
    </item>
  </channel>
</rss>
