<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Beyond Localhost - Medium]]></title>
        <description><![CDATA[Engineering that ships. War stories first: incidents, migrations, benchmarks and the on-call reality behind the slides. System design, infra, Kubernetes, backend. No hype, no product pitches. Want to write for us? Open the Write for Us tab. - Medium]]></description>
        <link>https://medium.com/beyond-localhost?source=rss----7ab1ae1562b8---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Beyond Localhost - Medium</title>
            <link>https://medium.com/beyond-localhost?source=rss----7ab1ae1562b8---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 04 Nov 2026 23:43:18 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/beyond-localhost" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Our CI/CD Pipeline Took 24 Minutes. These 7 GitHub Actions Fixes Got It Under 7.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/beyond-localhost/github-actions-ci-cd-pipeline-speed-62c671d12f3c?source=rss----7ab1ae1562b8---4"><img src="https://cdn-images-1.medium.com/max/2000/1*oFRNvTgjLvZOP5kLwfNNFA.jpeg" width="2000"></a></p><p class="medium-feed-snippet">No new tools and no bigger runners. Just YAML changes you can copy into your repo today.</p><p class="medium-feed-link"><a href="https://medium.com/beyond-localhost/github-actions-ci-cd-pipeline-speed-62c671d12f3c?source=rss----7ab1ae1562b8---4">Continue reading on Beyond Localhost »</a></p></div>]]></description>
            <link>https://medium.com/beyond-localhost/github-actions-ci-cd-pipeline-speed-62c671d12f3c?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/62c671d12f3c</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[ci-cd-pipeline]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[DevopsLesson]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 01:01:03 GMT</pubDate>
            <atom:updated>2026-11-04T01:01:02.353Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Festive Sales 2026: Digital Shelf Analytics and AI Optimization]]></title>
            <link>https://medium.com/beyond-localhost/festive-sales-2026-digital-shelf-analytics-and-ai-optimization-d1926c986eed?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/d1926c986eed</guid>
            <category><![CDATA[ecommerce-intelligence]]></category>
            <category><![CDATA[ai-optimization]]></category>
            <category><![CDATA[ecommerce-solution]]></category>
            <category><![CDATA[digital-shelf]]></category>
            <category><![CDATA[digital-shelf-analytics]]></category>
            <dc:creator><![CDATA[Minakshi Kushwaha]]></dc:creator>
            <pubDate>Sat, 31 Oct 2026 15:01:02 GMT</pubDate>
            <atom:updated>2026-10-31T15:01:02.549Z</atom:updated>
            <content:encoded><![CDATA[<h3>Win the Festive Season Sales with Digital Shelf Analytics &amp; Agentic Media Optimization by mFilterIt</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JTEV75ihWUrw5WVo.png" /></figure><p>The festive season is here, and once again it is the most demanding stretch of the year for ecommerce brands. Traffic peaks during the wave of sales on ecommerce and quick commerce platforms. Shopper intent is at its highest; the battle for the digital shelf, and ultimately the conversion, is real.</p><p>This year, the stakes are bigger. Redseer expected festive 2026 to be the strongest in five years, with online festive GMV of USD 15–16 billion, around 25% growth and 180–185 million shoppers. (<a href="https://redseer.com/reports/india-online-retail-2026-the-fastest-year-in-five-and-the-new-engines-behind-it/">Source: Redseer</a>) The first two days of sales alone crossed ₹24,000 crore, nearly six times a normal day. And two in every three online shoppers in those first two days came from smaller towns.</p><p>But the shape of the season has changed. Demand is spreading well beyond the metros, with online sales expected to grow by up to 29%. (<a href="https://www.fortuneindia.com/amp/story/business-news/up-to-29-growth-in-online-sales-expected-as-festive-demand-spreads-across-india/166095">Source: Fortune India</a>) Quick commerce is projected to grow 110–120% this festive period.</p><p>The season hasn’t been a straight line up, either. After the opening rush, sales fizzled as higher prices in the electronics category made shoppers hold back. Brands now have to win a shopper who is spread across more towns and compares prices harder than ever.</p><p>That means more shelves to watch, prices moving every hour, and more ad decisions than any team can make manually. Every missed keyword, every out-of-stock product and every lost rank still hands the sale to a competitor. What has changed is how fast it happens.</p><p>In this article, we look at the challenges brands face during festive sales in 2026, and how data-driven <a href="https://www.mfilterit.com/blog/power-of-digital-shelf-analytics">digital shelf analytics</a>, now paired with agentic AI, helps brands move from spotting a problem to fixing it in time.</p><h3>Challenges Ecommerce Brands Face During Festive Season Sales</h3><h3>Too Many Shelves, Too Little Visibility</h3><p>Your share of shelf is no longer one search page. It is Amazon, Flipkart, Myntra and Nykaa, plus Blinkit, Zepto, Instamart and newer quick commerce apps, each showing different results by city and even by dark store. A SKU can rank first in Bengaluru and be missing in Pune. Without SKU-level, location-level visibility, these gaps surface only after the sales are lost.</p><h3>Stockouts at the Worst Possible Moment</h3><p>Quick commerce transforms festive demand into quick decisions measured in minutes. If your product is not in stock in an important pin code during high-demand times, people will not stop; they will go with the next available item from the list. Moreover, many brands are spending money on advertising campaigns that bring them no profit.</p><h3>Competitive Pricing That Move by the Hour</h3><p>Flash deals, reseller undercutting and MAP violations spike during sales. Shoppers are better armed too: AI assistants like Amazon’s Rufus now show price history, so inflated “discounts” get exposed quickly. Brands need real-time visibility into their own and competitors’ pricing to respond without eroding margins or brand value.</p><h3>Product Pages Written for People, Now Read by AI</h3><p>AI shopping assistants and review summaries increasingly shape what shoppers see first. Incomplete titles, missing attributes, low-quality images and inconsistent content across sellers hurt both search ranking and how AI tools describe your product. Your PDP and reviews are now part of discovery, not just conversion.</p><h3>Media That Can’t See the Digital Shelf</h3><p>This is the biggest gap. Media sees the campaign: spend, clicks and ROAS. Ecommerce sees the market: rank, share of shelf, price and availability. During festive peaks, the two rarely meet. Bids keep climbing on keywords where you’re out of stock, budget stays locked on a platform where your price is uncompetitive, and a rival overtakes your hero keyword while campaigns run on autopilot. The result is wasted spend on low-intent areas and missed revenue where demand is real.</p><p>Click Here to Read Full Blog: <a href="https://tinyurl.com/2kr4hz9h">https://tinyurl.com/2kr4hz9h</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1926c986eed" width="1" height="1" alt=""><hr><p><a href="https://medium.com/beyond-localhost/festive-sales-2026-digital-shelf-analytics-and-ai-optimization-d1926c986eed">Festive Sales 2026: Digital Shelf Analytics and AI Optimization</a> was originally published in <a href="https://medium.com/beyond-localhost">Beyond Localhost</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[White Box Testing Beyond Localhost: The Code Paths That Only Run in Real Environments]]></title>
            <link>https://medium.com/beyond-localhost/white-box-testing-beyond-localhost-the-code-paths-that-only-run-in-real-environments-b5b3258613df?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/b5b3258613df</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[white-box-testing]]></category>
            <category><![CDATA[software-testing]]></category>
            <category><![CDATA[localhost]]></category>
            <dc:creator><![CDATA[Sancharini Panda]]></dc:creator>
            <pubDate>Sat, 31 Oct 2026 01:01:01 GMT</pubDate>
            <atom:updated>2026-10-31T01:01:01.785Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="White Box Testing Beyond Localhost" src="https://cdn-images-1.medium.com/max/1024/1*iGR5UMgmxacumutMsAkIpw.png" /></figure><p>There’s a whole category of code that has never executed on a developer’s laptop, and probably never will. It’s the branch that only fires when an environment variable says “production.” It’s the fallback that kicks in when a secret manager is slow to respond, and the logic that switches behavior per cloud region. Local test runs can show 85% coverage while that code sits in the untouched 15%, waiting for its first real outing during a deploy.</p><p>White box testing is supposed to reveal exactly this kind of gap, since it’s all about knowing which code paths ran. But coverage is usually measured in one place: wherever the tests happen to run, which is often a laptop or a CI container that looks nothing like the deployed environment. Taking white box testing beyond localhost means measuring what runs in the places where code actually lives.</p><h3>The code paths local environments never reach</h3><p>Most environment-only paths fall into a few recognizable groups.</p><p>Configuration branches are the obvious ones. Code that checks whether it’s running in staging or production, reads region-specific settings, or enables stricter limits outside development. Locally, those branches take the default path every time.</p><p>Feature flags add more. A flag that’s on in production but off everywhere else means the “on” path only ever runs for real users. Once a rollout finishes, the “off” path often goes untested too, even though it still ships.</p><p>Credential and secret handling is another quiet one. Cloud SDKs resolve credentials through a chain of sources. The AWS SDKs, for example, check environment variables, shared config files and then the instance or container role. On a laptop, credentials usually come from a local profile. In a deployed container, they come from the role. Different branch, different failure modes, and only one of them ever gets exercised locally.</p><p>Then there’s everything tied to real infrastructure: retry behavior against managed databases, timeouts against real network latency, caching that only exists behind a CDN, and multi-tenant settings that only exist with real tenant data.</p><h3>Measuring white box coverage in a deployed environment</h3><p>The good news is that most ecosystems can measure coverage in a running service, not just inside a test runner.</p><p>Go’s a nice example. You can build the whole binary with coverage switched on, and when the program shuts down, it drops its coverage data into a folder you choose. Java folks usually reach for the JaCoCo agent instead. Attach it to the running service and you can grab a coverage dump whenever you like, no restart needed. Node writes coverage data when the NODE_V8_COVERAGE environment variable points at a directory. Python’s coverage.py can wrap a running service process too.</p><p>In practice, teams tend to ship a coverage-enabled build to staging, or to some short-lived test environment, and then throw realistic traffic at it for a while. When that’s done, they pull the coverage data and turn it into a report. What about production? Mostly, no. Instrumentation isn’t free, so it usually stays in staging and test environments, with maybe a small canary running it briefly if a team really wants production numbers.</p><h3>Driving realistic traffic through the environment</h3><p>Coverage only shows what got exercised, so the traffic matters as much as the instrumentation. A handful of smoke tests won’t reach the interesting branches.</p><p>Replaying real API traffic, captured from production or from regular usage of staging, is one of the most effective ways to drive a deployed service through realistic paths. It brings along the odd requests, unusual parameters and edge cases that scripted tests rarely include. For branches that real traffic rarely reaches, like error handling and failovers, deliberate tests still have to fill the gap.</p><h3>Finding the branches that only run in real environments</h3><p>Here’s where the comparison gets useful. Generate one coverage report from local or CI test runs, and another from the deployed environment under realistic traffic. Then compare them branch by branch.</p><p>Branches covered in both are the well-understood ones. Branches covered only in the deployed environment are exactly the environment-specific paths this whole exercise is hunting for, and they deserve explicit tests that pin their behavior down. Branches covered in neither are the riskiest of all. They might be dead code. Or they might be the production-only fallback that nobody’s ever seen run.</p><p>A quick review of that last group with the team that owns the service usually sorts it out fast. Some branches get deleted. Others get turned into deliberate tests, using a staging configuration or a flag combination that finally reaches them.</p><h3>Black box vs white box testing in real environments</h3><p>Black box and white box testing come at this from opposite ends. A black box test doesn’t know or care what the code looks like. It just hits the deployed service and checks whether the answer matches what was promised. <a href="https://keploy.io/blog/community/white-box-testing"><strong>White box testing</strong></a> goes the other way, using knowledge of the code to see which internal paths actually got exercised.</p><p>In a deployed environment, the two work best together. Black box tests confirm the service behaves correctly, and white box coverage shows how much of the environment-specific code those tests reached. On the tooling side, white box coverage typically comes from tools like JaCoCo, coverage.py, Go’s built-in coverage and c8 for Node, while black box API testing tools such as Keploy generate tests from real traffic and also report statement and branch coverage for those replayed runs, which links the outside view to the inside one.</p><h3>Keeping environment-specific paths tested over time</h3><p>A one-time coverage comparison is useful. Making it routine is better.</p><p>Teams that keep this up usually run the deployed-environment coverage pass on a schedule, or before major releases. They track how many branches are only covered outside localhost, and they treat any new environment-only branch as something that needs a deliberate test before it ships. Feature flags get special attention, with each flag state covered at least once and dead flags cleaned out regularly so their branches don’t linger.</p><p>Parity helps too. The closer staging configuration is to production, the more environment-specific paths get exercised before release, rather than for the first time in front of customers.</p><h3>Conclusion</h3><p>Coverage numbers measured on a laptop describe the code that runs on a laptop. Real environments switch on branches local runs never touch, from credential chains to feature flags to region-specific logic, and those branches often carry the highest stakes. White box testing that follows code into deployed environments finally puts numbers on that hidden part of the codebase.</p><p>And honestly, the comparison is where it gets interesting. Line up the laptop numbers next to the staging numbers and something usually jumps out, often a branch that, as far as anyone can tell, has only ever run for real customers. Writing a proper test for that one branch tends to shift how a team thinks about coverage for good.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b5b3258613df" width="1" height="1" alt=""><hr><p><a href="https://medium.com/beyond-localhost/white-box-testing-beyond-localhost-the-code-paths-that-only-run-in-real-environments-b5b3258613df">White Box Testing Beyond Localhost: The Code Paths That Only Run in Real Environments</a> was originally published in <a href="https://medium.com/beyond-localhost">Beyond Localhost</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3 AI Tools Every DevOps Engineer Should Know]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/beyond-localhost/ai-tools-devops-engineers-2026-21b5400219ff?source=rss----7ab1ae1562b8---4"><img src="https://cdn-images-1.medium.com/max/2000/1*v5GGcV5iX2SyH1Hxe9rYqA.jpeg" width="2000"></a></p><p class="medium-feed-snippet">One for your code, one for production, one for your pipeline: Amazon CodeGuru, Sysdig, and Harness.</p><p class="medium-feed-link"><a href="https://medium.com/beyond-localhost/ai-tools-devops-engineers-2026-21b5400219ff?source=rss----7ab1ae1562b8---4">Continue reading on Beyond Localhost »</a></p></div>]]></description>
            <link>https://medium.com/beyond-localhost/ai-tools-devops-engineers-2026-21b5400219ff?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/21b5400219ff</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[ai-tools]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[DevopsLesson]]></dc:creator>
            <pubDate>Fri, 30 Oct 2026 18:01:01 GMT</pubDate>
            <atom:updated>2026-10-30T20:39:29.388Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[N+1 Queries: Your ORM Is Lying to You and Your Database Is Paying the Bill]]></title>
            <link>https://medium.com/beyond-localhost/n-1-queries-your-orm-is-lying-to-you-and-your-database-is-paying-the-bill-03a0f184285e?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/03a0f184285e</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[backend-engineering]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[database-design]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[Nsikan Patrick Adaowo]]></dc:creator>
            <pubDate>Fri, 30 Oct 2026 11:01:02 GMT</pubDate>
            <atom:updated>2026-10-30T11:01:01.960Z</atom:updated>
            <content:encoded><![CDATA[<p>Your ORM is supposed to make database access easier, so why is your application making hundreds of database queries to return a single response?</p><p>You define your models, establish your relationships, and let the ORM handle the details. No complicated SQL. No messy joins. Just clean, readable application code.</p><p>Except there’s a problem.</p><p>That innocent-looking loop might be querying the database once for every record it retrieves. Your code stays simple, but your database pays for every additional round trip.</p><p>And adding an index might make those queries faster without fixing the underlying problem.</p><p>The issue isn’t always how efficiently you’re querying your database. Sometimes, it’s how many times you’re querying it in the first place.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*A-0eBZkVVeda8TDw.png" /></figure><p>This is the N+1 query problem. And it’s particularly frustrating because the code that introduces it can look perfectly reasonable.</p><p>An ORM promises to make database interactions easier to work with. It gives you models, relationships, and convenient methods for retrieving data. But convenient database access is not the same thing as efficient database access.</p><p>Your application code might look simple while the database handles a workload that was never necessary.</p><h3>What Exactly Is an N+1 Query Problem?</h3><p>Imagine you’re building a SaaS application with users and their projects.</p><p>You need an endpoint that returns a list of users alongside the projects each user owns.</p><p>The data model is straightforward:</p><ul><li>A User can own many Project records</li><li>Each Project belongs to one User.</li></ul><p>In a relational database, this is a familiar one-to-many relationship.</p><p>The naive implementation might look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UzeJXFGDNuwFN35Q.png" /></figure><p>Nothing about this code looks particularly alarming.</p><p>You retrieve the users, loop through them, and fetch each user’s projects. The code is readable, the relationship is clear, and the response has the shape you need.</p><p>But look at what happens at the database level.</p><p>The first query retrieves the users:</p><pre>SELECT *<br>FROM &quot;User&quot;;</pre><p>Then, for every user returned, the application executes another query:</p><pre>SELECT *<br>FROM &quot;Project&quot;<br>WHERE &quot;userId&quot; = $1;</pre><p>If the first query returns 50 users, the database receives:</p><ul><li>1 query to retrieve the users.</li><li>50 additional queries to retrieve their projects.</li></ul><p>That’s 51 queries to construct one response.</p><p>If the endpoint returns 500 users, it can execute 501 queries.</p><p>This is why the problem is called N+1: one initial query, followed by N additional queries.</p><p>The exact number depends on the data and the code path, but the underlying issue remains the same: the number of database queries grows with the number of records returned by the initial query.</p><p>And that growth can happen without any obvious change to the service’s complexity or readability.</p><h3>Why N+1 Queries Are More Expensive Than They Look</h3><p>A common mistake is to assume that an individual query is cheap, so executing many of them must also be cheap.</p><p>That assumption ignores the work surrounding each query.</p><p>A database query isn’t just the time PostgreSQL spends executing SQL. The application and database must also coordinate the operation.</p><p>Depending on the driver, connection pool, network, and execution path, each query may involve:</p><ol><li>Sending a request from the application to the database.</li><li>Parsing, planning, or executing the statement as applicable.</li><li>Reading or modifying the relevant data.</li><li>Returning the result to the application.</li><li>Converting the result into application-level objects.</li></ol><p>Some of these costs can be reduced through prepared statements, connection reuse, or other optimizations. But repeating a query still creates work.</p><p>Consider a simplified example.</p><p>Suppose each additional query takes an average of 5 milliseconds from the application’s perspective, including the relevant round-trip and processing overhead.</p><p>With 100 users, the endpoint executes 101 queries.</p><p>If those additional queries run sequentially, the repeated database operations alone could account for roughly 500 milliseconds, before adding the initial query, serialization, and other application work.</p><p>That number is illustrative, not a performance benchmark. Actual latency depends on the environment and workload.</p><p>The important point is that a query taking only a few milliseconds can become a serious problem when repeated hundreds of times.</p><p>And sequential execution isn’t the only concern.</p><h3>The database is doing unnecessary work</h3><p>Every additional query consumes some combination of database CPU, connection time, query execution resources, memory, and I/O.</p><p>Even when an index makes each lookup efficient, the database still has to process every request.</p><p>An index can make a query faster. It cannot eliminate the need to execute that query 100 times.</p><h3>Your connection pool becomes a bottleneck</h3><p>Database connections are finite resources.</p><p>When an application generates large numbers of queries, those queries must compete for available connections and database capacity.</p><p>This becomes particularly problematic when multiple requests are doing the same thing simultaneously.</p><p>One endpoint might execute 101 queries. Fifty concurrent requests could generate thousands of database operations in a short period.</p><p>The result can be increased connection wait time, growing latency, and reduced throughput.</p><h3>Your tail latency gets worse</h3><p>Users don’t experience your endpoint as a collection of individually fast queries. They experience the total time required to receive a response.</p><p>A route that performs unnecessary database operations can have acceptable average latency under light traffic and terrible p95 or p99 latency under load.</p><p>This is why N+1 problems often become obvious only after an application starts receiving meaningful traffic.</p><p>The application didn’t suddenly become badly written. The workload exposed an inefficiency that was already present.</p><h3>The ORM Didn’t Lie. It Hid the Cost.</h3><p>Let’s be precise about the title.</p><p>Your ORM isn’t necessarily generating bad SQL. In the example above, each individual SQL statement is reasonable.</p><p>The problem is the way the application uses the ORM.</p><p>Object-relational mappers make relational data easier to work with by representing tables and relationships as application-level objects. That abstraction is useful, but it can make the cost of accessing related records less obvious.</p><p>Consider this TypeORM example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tDOIDEz9HJoO9kbx.png" /></figure><p>The repository methods are straightforward. The relationship is expressed through objects rather than handwritten SQL.</p><p>But the database still sees a query for the users followed by a query for each user’s projects.</p><p>The same pattern can appear in Prisma, TypeORM, Sequelize, Django ORM, Hibernate, and other data-access tools.</p><p>The syntax changes. The database workload does not.</p><p>This is an important distinction: <strong>an ORM can simplify how you express a query without guaranteeing that the overall query strategy is efficient.</strong></p><p>A method call that looks like a single operation at the application level may generate multiple SQL statements. And a loop containing a database call can multiply those statements in ways that are easy to overlook during code review.</p><p>The right question isn’t simply, “Does this code use an ORM correctly?”</p><p>It’s also, “How many database operations does this code generate for a typical request?”</p><h3>The First Fix: Fetch Related Data Intentionally</h3><p>Let’s return to the original requirement.</p><p>We want to retrieve users and their projects.</p><p>We don’t actually need to retrieve each user’s projects independently. We already know the complete set of users we’re interested in.</p><p>That gives us a few better options.</p><h3>Option A: Use a nested relation query</h3><p>With Prisma, you can request the related projects as part of the same logical query:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/908/0*wnGlac_3pkepXHdz.png" /></figure><p>Instead of manually fetching projects inside a loop, you describe the response shape you need.</p><p>Depending on the ORM version and relation-loading strategy, Prisma may load the related records using a database join or multiple batched SQL queries. Either way, this avoids the original pattern of issuing one independent project query for every user.</p><p>That distinction matters.</p><p>A nested ORM query doesn’t universally mean one SQL statement. Some ORMs fetch relations using a join; others issue separate queries and combine the results in application code. Some support both strategies.</p><p>What matters first is eliminating the repeated per-record lookup. After that, you can evaluate which relation-loading strategy best fits the workload.</p><p><a href="https://www.prisma.io/docs/orm/v7/prisma-client/queries/relation-queries">This Prisma’s documentation explains the available relation-query strategies and their trade-offs</a></p><h3>Option B: Fetch the parent records, then batch the children</h3><p>Sometimes you need more control over how the data is assembled.</p><p>You can retrieve the users first, collect their IDs, and fetch all matching projects in a single additional query.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7e2RWS9zhY1CxCOI.png" /></figure><p>Assume userId is a numeric field in this example. If your schema uses UUIDs or strings, use the corresponding TypeScript type.</p><p>The important part is this filter:</p><pre>where: {<br>  userId: {<br>    in: userIds,<br>  },<br>}</pre><p>Instead of executing one query per user, the application asks PostgreSQL to retrieve projects belonging to the entire set of user IDs.</p><p>For a non-empty user list, this approach uses two logical database reads: one for users and one for projects.</p><p>The application then groups the projects by userId and assembles the response.</p><p>This is a useful pattern when:</p><ul><li>You need custom response shaping.</li><li>You want separate control over parent and child queries.</li><li>The relation-loading behavior of your ORM isn’t suitable for the endpoint.</li><li>You need to reuse the fetched records for additional application-level processing.</li></ul><p>There are trade-offs. You now own the grouping logic, and a large list of IDs may require batching or pagination. You also need to ensure that the response doesn’t load an unbounded number of child records.</p><p>But the query count no longer grows by one for every user.</p><p>That’s the key improvement.</p><h3>A JOIN Is Not Automatically the Best Answer</h3><p>At this point, it can be tempting to conclude that every N+1 problem should be fixed with a SQL JOIN.</p><p>Often, a join is a good solution. But treating it as a universal rule replaces one simplistic assumption with another.</p><p>Consider:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/704/0*zb75dvl_SXPOcMIp.png" /></figure><p>This retrieves users and their projects through a single SQL statement.</p><p>For a straightforward one-to-many relationship, it can be an effective solution.</p><p>But joins change the shape of the result set.</p><p>If one user has ten projects, that user’s columns can appear across ten result rows. If you join multiple one-to-many relationships, the number of intermediate rows can grow dramatically.</p><p>For example, a user has 10 projects and 20 notifications. Joining both collections without carefully controlling the query shape can produce up to 200 combinations for that user.</p><p>This is sometimes called row multiplication or join explosion.</p><p>The database may still execute the query efficiently for a particular workload, but the application can end up processing far more data than it actually needs.</p><p>The practical lesson is not to avoid joins. It is to understand what the join produces.</p><p>Use a join when the relationship and result shape make sense. Consider separate batched queries when they avoid unnecessary row multiplication or make pagination easier to reason about. Measure both approaches when the workload justifies it.</p><p><strong>The objective is not to minimize the SQL statement count at any cost. It is to minimize unnecessary work while preserving the data and consistency your endpoint requires.</strong></p><h3>Why Adding an Index Doesn’t Fix the Root Problem</h3><p>Suppose the original query is slow:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-t9mDyNg_-GCZz8z.png" /></figure><p>That may be the right index for the query, depending on the table size, data distribution, and execution plan.</p><p>But the N+1 pattern remains.</p><p>If the application retrieves 100 users and issues one project query for each, the database still receives 100 project queries. The index may make those lookups faster, but it doesn’t remove their round-trips or execution overhead.</p><p>There is another detail worth remembering: indexes aren’t free. They consume storage and add maintenance work to inserts, updates, and deletes.</p><p>An index should support <a href="https://nsikanadaowo.substack.com/p/database-design-starts-with-access">a meaningful access pattern</a>, not compensate blindly for a query strategy that should be redesigned.</p><p>A useful debugging sequence is:</p><ol><li>Count how many times the query executes per request.</li><li>Identify whether the repetitions are necessary.</li><li>Change the query shape to batch or load the related data appropriately.</li><li>Inspect the execution plan of the remaining important queries.</li><li>Add or adjust indexes when the evidence supports doing so.</li></ol><p>Use PostgreSQL’s EXPLAIN (ANALYZE, BUFFERS) to investigate query execution, remembering that ANALYZE executes the statement. For statements that modify data, take appropriate precautions and understand the side effects before running it.</p><p>Query-plan analysis and query-count analysis answer different questions. You need both when both are relevant.</p><h3>The GraphQL Trap: One Resolver, One Query, Repeated Many Times</h3><p>N+1 problems are particularly easy to introduce in GraphQL.</p><p>Imagine a GraphQL API that returns users and their projects:</p><pre>query {<br>  users {<br>    id<br>    name<br>    projects {<br>      id<br>      name<br>    }<br>  }<br>}</pre><p>The users resolver fetches the users.</p><p>Then the projects field resolver runs for each returned user.</p><p>If that resolver performs an independent database query, the request can produce the familiar N+1 pattern.</p><p>The confusing part is that each resolver may be perfectly reasonable in isolation.</p><p>The users resolver knows how to fetch users. The projects resolver knows how to fetch projects for a user. Both do their jobs correctly.</p><p>The inefficiency appears when the GraphQL execution model invokes the projects resolver repeatedly without coordinating those database reads.</p><p>One common solution is a DataLoader-style batching mechanism.</p><p>Conceptually, the resolver asks for projects for a particular user:</p><pre>const projects = await projectLoader.load(user.id);</pre><p>Instead of immediately executing a separate query for every call, the loader collects multiple user IDs and fetches the corresponding projects in a batch.</p><p>The batch function might look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QueMuVWDBh3TUJ8P.png" /></figure><p>The returned array must correspond to the order of the incoming IDs, including empty results. That ordering contract is important for DataLoader implementations.</p><p>In production, you would normally use a DataLoader implementation or equivalent batching utility rather than building all of its caching and scheduling behavior yourself.</p><p>Also, create loaders with the appropriate request scope when cached results depend on the current user or authorization context. A global loader cache can accidentally reuse data across requests if it’s designed incorrectly.</p><p>DataLoader isn’t just a performance trick. It addresses a structural problem: field-level resolvers often need a way to coordinate data access across many related objects.</p><h3>Why Promise.all() Is Not a Real Fix</h3><p>One of the most common attempts to fix a slow loop is to make the queries concurrent.</p><p>Instead of:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*h9WjJzE6OX-XIcfi.png" /></figure><p>A developer writes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/0*qg_hQfFHLR-FJ-af.png" /></figure><p>The second implementation removes the sequential wait between queries.</p><p>That can reduce wall-clock latency for small, bounded workloads. But it still issues one query per user.</p><p>With 100 users, you’re still generating roughly 100 project queries.</p><p>Worse, you’re now attempting to issue them concurrently. Depending on the database pool and workload, this can increase contention, create a queue of waiting queries, and consume resources more aggressively.</p><p>Concurrency changes when the work runs. It does not eliminate the work.</p><p>There are legitimate reasons to execute independent queries concurrently. The mistake is using concurrency as a substitute for reducing unnecessary database operations.</p><p>Before asking, “How can I make these queries run faster?”, ask, “Why am I running so many queries at all?”</p><h3>Not Every Repeated Query Is an N+1 Problem</h3><p>A good engineer also knows when not to apply a pattern mechanically.</p><p>Imagine you’re processing a small batch of three records. Fetching related data separately might be perfectly acceptable if it makes the code simpler and the performance requirements are met.</p><p>Or suppose each item requires a different query with distinct filters, authorization rules, or business logic. Those queries may not be safely reducible to one batch.</p><p>The defining concern isn’t simply that a query appears multiple times. It’s that a query is being repeated per result when the underlying data could be retrieved more efficiently as a group.</p><p>You should also distinguish N+1 from other database performance problems.</p><ul><li><strong>Missing indexes:</strong> The database must examine too many rows to answer a query.</li><li><strong>Over-fetching:</strong> The application retrieves more columns or records than it needs.</li><li><strong>Inefficient joins:</strong> A query creates excessive intermediate rows or performs expensive operations.</li><li><strong>Connection pool exhaustion:</strong> Requests wait for database connections to become available.</li><li><strong>N+1 queries:</strong> The application issues unnecessary additional queries as the number of parent records grows.</li></ul><p>These problems can occur together, but they require different diagnoses.</p><p>A slow endpoint is a symptom, not a diagnosis. Don’t start adding indexes, introducing Redis, or rewriting your ORM queries until you understand which cost is actually dominating the request.</p><h3>How to Detect N+1 Queries Before Production</h3><p>The most reliable approach is to observe what the application sends to the database.</p><h3>Log Prisma queries during development</h3><p>Prisma supports query-event logging that can help you inspect generated SQL and execution durations.</p><p>For example, depending on your Prisma version and client setup:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/0*gDqx-nZBxlijWs1N.png" /></figure><p>This is a simplified example of setting up a query listener. In a NestJS application, integrate the listener into your Prisma service or database instrumentation setup rather than creating a separate client for every service.</p><p>Avoid indiscriminately logging sensitive parameter values in production. <a href="https://www.prisma.io/docs/orm/v7/prisma-client/queries/advanced/query-optimization-performance">Query logs can expose personal information or other confidential data</a>, and excessive logging can introduce its own performance overhead.</p><h3>Look for repeated query shapes</h3><p>Suppose your logs contain:</p><pre>SELECT ... FROM &quot;User&quot;;</pre><pre>SELECT ... FROM &quot;Project&quot;<br>WHERE &quot;userId&quot; = $1;</pre><pre>SELECT ... FROM &quot;Project&quot;<br>WHERE &quot;userId&quot; = $1;</pre><pre>SELECT ... FROM &quot;Project&quot;<br>WHERE &quot;userId&quot; = $1;</pre><p>The repeated query shape is a clue.</p><p>The parameter changes, but the SQL structure stays the same. If the same route triggers this pattern repeatedly, investigate whether the query is being executed once for every parent record.</p><p>Don’t assume that repeated SQL automatically means N+1. A query may legitimately execute multiple times. Correlate the events with the request, route, and data-access code.</p><h3>Measure query count per request</h3><p>Execution duration alone doesn’t tell the full story.</p><p>A route that executes 40 queries taking 2 milliseconds each may look healthy when you inspect individual query durations. Yet the route could be doing far more database work than its response requires.</p><p>Useful measurements include:</p><ul><li>Number of database queries per request.</li><li>Repeated query shapes and execution counts.</li><li>Total database time per request.</li><li>Endpoint latency, including p50, p95, and p99.</li><li>Connection pool wait time and utilization.</li><li>Rows returned and, where available, rows examined or read.</li></ul><p>For deeper investigation, query monitoring and tracing tools can connect a slow endpoint to the SQL statements it generates. <a href="https://www.prisma.io/docs/orm/v7/prisma-client/queries/advanced/query-optimization-performance">Prisma’s query-optimization documentation</a> describes query logging and other diagnostic techniques:</p><h3>Test with realistic data volumes</h3><p>An N+1 problem may be invisible when your test database contains only two users.</p><p>With two users, the endpoint performs three queries. That may seem harmless.</p><p>With 200 users, the same code performs 201 queries.</p><p>Performance tests should use representative record counts and relationships. Where practical, test query counts as well as response time. A test that verifies only the returned JSON can pass even when the implementation generates hundreds of unnecessary queries.</p><h3>A Practical Checklist for Reviewing Database Access</h3><p>When reviewing a NestJS service, GraphQL resolver, or repository method, ask:</p><p><strong>1. Am I querying inside a loop?</strong></p><p>Look for database calls inside for, for...of, map, forEach, or other repeated execution paths. Not every occurrence is wrong, but each deserves scrutiny.</p><p><strong>2. Can I fetch the related records in one batch?</strong></p><p>If you already have a collection of IDs, consider a query using IN, an ORM’s nested relation loading, or a DataLoader-style batching mechanism.</p><p><strong>3. Am I returning only the data the endpoint needs?</strong></p><p>Use select or the equivalent to avoid loading large text fields, unnecessary relations, or entire entities when the response requires only a few fields.</p><p><strong>4. Is the response bounded?</strong></p><p>Pagination matters. Fetching all users and all their projects in one giant query can replace an N+1 problem with an unbounded data-loading problem.</p><p><strong>5. Does the chosen loading strategy fit the relationship?</strong></p><p>A join may be effective for one relation but expensive when several large collections are loaded together. Compare the query shapes and actual execution costs.</p><p><strong>6. Have I measured the result?</strong></p><p>Count the queries before and after the change. Compare endpoint latency and database resource usage under representative load.</p><p><strong>7. Is the data access still correct?</strong></p><p>Optimization must preserve authorization, filtering, ordering, pagination, and any required consistency guarantees. A faster query that returns the wrong records is not an optimization.</p><h3>Final Thoughts: Your ORM Is Only as Smart as the Queries You Ask It to Make</h3><p>N+1 queries are frustrating because they rarely look like a serious engineering mistake at first.</p><p>The code is readable. The relationships are modeled correctly. The endpoint returns the expected response.</p><p>The problem is hidden in the execution pattern.</p><p>An ORM can make database interactions easier to express, but it cannot remove the need to understand how those interactions translate into actual database work.</p><p>And the fix isn’t always to write raw SQL, add an index, introduce a cache, or force every relation into a join. Sometimes, the most effective improvement is simply to stop asking the database the same question one record at a time.</p><p>Think about the workload your code creates, not just the code you write.</p><p>When an endpoint becomes slow, don’t stop at asking whether each query is efficient. Ask whether all those queries need to exist in the first place.</p><p>Because the fastest unnecessary query is the one your application never sends.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=03a0f184285e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/beyond-localhost/n-1-queries-your-orm-is-lying-to-you-and-your-database-is-paying-the-bill-03a0f184285e">N+1 Queries: Your ORM Is Lying to You and Your Database Is Paying the Bill</a> was originally published in <a href="https://medium.com/beyond-localhost">Beyond Localhost</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The MCP Connection Was Open. The Session Was Gone.]]></title>
            <link>https://medium.com/beyond-localhost/the-mcp-connection-was-open-the-session-was-gone-1a81817a3247?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/1a81817a3247</guid>
            <category><![CDATA[model-context-protocol]]></category>
            <category><![CDATA[debugging]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[bulutarkan]]></dc:creator>
            <pubDate>Fri, 30 Oct 2026 01:01:02 GMT</pubDate>
            <atom:updated>2026-10-30T01:01:02.318Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1YuMXsDd4GSxGAuv" /><figcaption>Photo by <a href="https://unsplash.com/@jouwdan?utm_source=medium&amp;utm_medium=referral">Jordan Harrison</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>A Claude Desktop user had Mac MCP connected, left it alone for a while, and came back to a peculiar failure. The application still appeared to have a connection. The next tool call did not work.</p><p>They had already found a way around it. One shell script started a local relay and supplied the MCP credential. A second script sent a ping every ten minutes. It was a practical fix for their own machine, but it bothered me that they needed either script in the first place.</p><p>Mac MCP lets AI clients work with native macOS apps through a local server. The same user said Codex worked without the extra steps, which initially sent me looking for a Claude Desktop-specific bug. I spent a while in the wrong place. The clients weren&#39;t the whole story.</p><p>When I followed the setup path end to end, I found three separate problems. Our instructions pushed Claude Desktop users toward a needlessly complicated connection. The server could forget an idle MCP session after 30 minutes. And an old exported API key could override the one the setup command had just read. Put together, they made a fairly ordinary connection look unreliable.</p><h4>A setup command that sent people in the wrong direction</h4><p>I started with the easiest failure to reproduce from the source: our documentation and CLI guidance.</p><p>Mac MCP already had a local stdio bridge. Its purpose is straightforward. A client such as Claude Desktop launches a command, exchanges MCP messages over standard input and output, and the bridge forwards those messages to the local HTTP server. The HTTP credential stays with Mac MCP instead of being copied into the client’s configuration.</p><p>Yet an older connection-config path could make Claude Desktop look unsupported. Someone following that path could reasonably conclude that they needed to use an HTTP proxy, assemble an Authorization header and keep the connection alive themselves.</p><p>The supported setup is now:</p><pre>mac-mcp connect --client claude-desktop</pre><p>That command creates a Claude Desktop entry invoking mac-mcp stdio. It is a local process, not a public web endpoint. The client’s configuration does not need a literal API key or remote URL. This does not make the credential disappear; it moves responsibility for reading and protecting it to the component that owns the local server.</p><p>That was annoying to admit. We&#39;d already built a local bridge for exactly this job, yet our instructions sent people somewhere else. The user&#39;s relay script was, in part, a workaround for our documentation.</p><h4>The connection and the session had different lifetimes</h4><p>Mac MCP’s server-side MCP session idle timeout defaults to 1,800 seconds, or 30 minutes. It can be configured from 60 seconds up to a day. After an eligible idle interval, the server can discard the MCP session identifier. A transport or client UI may still look connected even though the server no longer recognizes the session associated with the next request.</p><p>The word “connected” hides the problem. The Claude process can still be running. The HTTP endpoint can still answer. The API key can still be valid. None of that tells you whether the server recognizes the session ID attached to the next request.</p><p>There is a nuance worth making explicit. A genuinely open event stream can keep the current MCP session alive; the current server integration documents that behavior. The failure is not that every open SSE stream expires after thirty minutes. The danger is assuming that a visible client connection, a relay process or a previously successful handshake proves the server still has the session. A client may be idle without an active request that preserves it, or its server may have restarted.</p><p>Increasing the idle timeout would have hidden some of the reported failures. It wouldn’t have solved the general case. A Mac sleeps overnight. An app is updated. A process restarts. A network connection breaks. Eventually the client needs to recognize that session state is gone and negotiate a fresh one.</p><p>I looked at the problem from the message boundary rather than the clock. The bridge has to retain the original initialize message and protocol version. When a later request receives a clear “session not found” response, it can initialize a fresh session, send the initialized notification, then forward the request under the new session ID.</p><h4>The retry rule that mattered more than reconnecting</h4><p>If a connection attempt fails before reaching the server, a bounded retry is reasonable. In the bridge, connection errors can wait briefly for a server that is starting or restarting. If the server stays unavailable, the caller receives a clear error instead of an indefinitely spinning status.</p><p>If the server explicitly returns a missing-session error before processing the tool request, the bridge can rebuild the MCP session and try that request again.</p><p>But a connection dropping during the response is different. The server may have already performed the work. An email could have been sent, a file edited or a command started, even if the client never received the answer.</p><p>I did not want a reliability fix that quietly duplicated someone’s action. For a transport error mid-reply, the bridge reports that the request was interrupted and tells the caller to check the outcome before trying again. It does not auto-resend the original tool call.</p><p>This is the security boundary I care about as much as the session boundary. Recovering a transport must not silently grant more authority or repeat an operation with unknown effects. Permission checks still happen on the server, under the same user’s configuration, after a new authenticated session is established. A recovered connection is not permission to skip approvals, and a successful retry is not proof of exactly-once execution.</p><h4>The correct API key came from the wrong place</h4><p>The user had the right key in Mac MCP’s .env. The local setup read that file, so the client side looked correct. But an older MCP_API_KEY remained exported in the environment that launched the server. The old value took precedence. The client and server were using different credentials, and the visible configuration made it hard to explain why.</p><p>The change was deliberately specific. Mac MCP now treats MCP_API_KEY from its own .env as authoritative over an inherited value when they differ. It emits a warning explaining which source won, without printing either key. This is not a broad rewrite of environment-variable precedence; other settings do not automatically receive the same rule.</p><p>That choice is about both consistency and containment. A setup tool and server should not disagree about the credential merely because of an old shell export. But logging the actual secrets in order to diagnose the disagreement would create another problem.</p><p>The bridge also forwards the credential as an HTTP Bearer header to the local MCP endpoint. Nothing about session renewal should turn an authentication failure into an unauthenticated connection or cause it to fall back to a weaker mode. A wrong key is a wrong key; the bridge reports a connection rejection and stops there.</p><h4>What the evidence establishes, and what it doesn’t</h4><p>The session timeout tests verify the 1,800-second default, the lower and upper bounds and the behavior for invalid configuration. The source also sets the actual MCP session manager’s timeout from that configured value. Those tests establish the configuration rule, not the behavior of every remote MCP client.</p><p>The stdio bridge tests use a fake Streamable HTTP server. One test invalidates the server’s known sessions, then checks that the bridge replays initialization before forwarding the next request. Other tests cover a temporarily unavailable server, a server that never comes back, a rejected credential and a listener that encounters a missing session.</p><p>The test I find most important deliberately interrupts a request while its reply is being read. It checks that the request is sent once, that the bridge returns an interrupted-result error and that no automatic second call is made. This isn’t a proof of exactly-once execution in the world. It’s a regression guard against our own code creating an obvious duplicate.</p><p>The session-timeout test documentation also records a shortened-timeout end-to-end exercise using a 60-second interval. I regard that as evidence for the specific recovery scenario tested, not as a permanent cross-version compatibility guarantee. The committed unit tests are easier for someone else to inspect and reproduce.</p><p>There are still boundaries I can’t prove from those tests. They don’t establish that every Claude Desktop release behaves identically, that every proxy handles streaming correctly or that an arbitrary mutating tool can be replayed safely after a lost response. I haven’t independently confirmed that the original user’s scripts have been removed from their machine. Their intention to simplify their setup is encouraging feedback, not final validation.</p><h4>What I should have caught earlier</h4><p>I had treated these as three unrelated bugs. The docs were wrong over here, the timeout was over there, and the environment variable behaved differently depending on how the server was launched. From the user&#39;s side, it was one problem: getting Claude Desktop to work required two homemade scripts.</p><p>I should have installed it as a first-time user would, following only our documentation. I should have left the client idle long enough to see what happened. And I should have checked which API key the running server actually used before assuming the client&#39;s key was wrong.</p><p>The replacement has less ceremony: one supported setup command, no hand-built relay, no keepalive cron. The bridge still has to handle restarts, missing sessions and broken connections. What changed is that it knows which failures are safe to recover from, and which ones need a person to check what happened.</p><h4>Four questions I’ll ask next time</h4><p>When a user says their MCP tools stop working after idle time, my first response won’t be “restart everything.” I’ll ask whether the local server is reachable; whether the server and client are using the same effective credential; whether the server still recognizes the MCP session; and whether the transport can negotiate a new session without replaying an operation whose outcome is unknown.</p><p>The code and tests are public:<br>- <a href="https://github.com/bulutarkan/mac-mcp/blob/main/mcp_server/stdio_bridge.py">https://github.com/bulutarkan/mac-mcp/blob/main/mcp_server/stdio_bridge.py</a><br>- <a href="https://github.com/bulutarkan/mac-mcp/blob/main/mcp_server/security.py">https://github.com/bulutarkan/mac-mcp/blob/main/mcp_server/security.py</a><br>- <a href="https://github.com/bulutarkan/mac-mcp/blob/main/tests/test_stdio_bridge.py">https://github.com/bulutarkan/mac-mcp/blob/main/tests/test_stdio_bridge.py</a><br>- <a href="https://github.com/bulutarkan/mac-mcp/blob/main/tests/test_session_idle_timeout.py">https://github.com/bulutarkan/mac-mcp/blob/main/tests/test_session_idle_timeout.py</a><br>- <a href="https://github.com/bulutarkan/mac-mcp/blob/main/docs/connect.md">https://github.com/bulutarkan/mac-mcp/blob/main/docs/connect.md</a></p><p>I had treated a working-looking connection as evidence that the next call would work. It wasn&#39;t. A client can appear fine after the server has forgotten its session. The next call should recover from that without guessing whether an interrupted operation needs to run again.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1a81817a3247" width="1" height="1" alt=""><hr><p><a href="https://medium.com/beyond-localhost/the-mcp-connection-was-open-the-session-was-gone-1a81817a3247">The MCP Connection Was Open. The Session Was Gone.</a> was originally published in <a href="https://medium.com/beyond-localhost">Beyond Localhost</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Your Redis Lock Expired. Two Workers Just Updated the Same Data.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/beyond-localhost/your-redis-lock-expired-two-workers-just-updated-the-same-data-ce36832d0835?source=rss----7ab1ae1562b8---4"><img src="https://cdn-images-1.medium.com/max/1536/1*8d9o6aJ6cZvBC6MHv7gfaw.png" width="1536"></a></p><p class="medium-feed-snippet">The lock was working. The database was healthy. And somehow, two workers still processed the same job.</p><p class="medium-feed-link"><a href="https://medium.com/beyond-localhost/your-redis-lock-expired-two-workers-just-updated-the-same-data-ce36832d0835?source=rss----7ab1ae1562b8---4">Continue reading on Beyond Localhost »</a></p></div>]]></description>
            <link>https://medium.com/beyond-localhost/your-redis-lock-expired-two-workers-just-updated-the-same-data-ce36832d0835?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/ce36832d0835</guid>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Lotfi Habbiche]]></dc:creator>
            <pubDate>Thu, 29 Oct 2026 06:22:22 GMT</pubDate>
            <atom:updated>2026-10-27T11:29:29.453Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[An AI Agent Deleted a Production Database in 9 Seconds.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/beyond-localhost/ai-agent-least-privilege-production-playbook-12ba6355e071?source=rss----7ab1ae1562b8---4"><img src="https://cdn-images-1.medium.com/max/2000/1*sIHOXWSBVcC4IbEhwHdDZw.jpeg" width="2000"></a></p><p class="medium-feed-snippet">Your AI agent just asked for a production token. What you hand it decides whether you have a normal Tuesday or a 30-hour outage.</p><p class="medium-feed-link"><a href="https://medium.com/beyond-localhost/ai-agent-least-privilege-production-playbook-12ba6355e071?source=rss----7ab1ae1562b8---4">Continue reading on Beyond Localhost »</a></p></div>]]></description>
            <link>https://medium.com/beyond-localhost/ai-agent-least-privilege-production-playbook-12ba6355e071?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/12ba6355e071</guid>
            <category><![CDATA[cloud-security]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[platform-engineering]]></category>
            <dc:creator><![CDATA[DevopsLesson]]></dc:creator>
            <pubDate>Wed, 28 Oct 2026 13:01:03 GMT</pubDate>
            <atom:updated>2026-10-28T21:02:10.514Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[The Rise and Fall of Spring MVC Tags: A Nostalgic UI Grid Postmortem]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/beyond-localhost/the-rise-and-fall-of-spring-mvc-tags-a-nostalgic-ui-grid-postmortem-cb6929b73b3e?source=rss----7ab1ae1562b8---4"><img src="https://cdn-images-1.medium.com/max/2600/1*s_50R_3d2MYaXvmuxvfoPQ.jpeg" width="6016"></a></p><p class="medium-feed-snippet">Remember &lt;form:select&gt;, &lt;c:forEach&gt;, and hand-rolled JSP tables? How Spring&apos;s taglibs shaped (then abandoned) server-side UIs.</p><p class="medium-feed-link"><a href="https://medium.com/beyond-localhost/the-rise-and-fall-of-spring-mvc-tags-a-nostalgic-ui-grid-postmortem-cb6929b73b3e?source=rss----7ab1ae1562b8---4">Continue reading on Beyond Localhost »</a></p></div>]]></description>
            <link>https://medium.com/beyond-localhost/the-rise-and-fall-of-spring-mvc-tags-a-nostalgic-ui-grid-postmortem-cb6929b73b3e?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/cb6929b73b3e</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ui-design]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[spring-mvc]]></category>
            <dc:creator><![CDATA[Sriram Mahalingam]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 17:31:01 GMT</pubDate>
            <atom:updated>2026-10-27T17:31:01.610Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[5 Free Claude Code Skills for DevOps Engineers (2026)]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/beyond-localhost/claude-code-skills-devops-dd5b61326148?source=rss----7ab1ae1562b8---4"><img src="https://cdn-images-1.medium.com/max/2600/1*RZ-bfl8EdbtJh58ZltIU9Q.png" width="2800"></a></p><p class="medium-feed-snippet">Stop getting FROM node:latest. Teach Claude Code your production standards once, and it follows them every time.</p><p class="medium-feed-link"><a href="https://medium.com/beyond-localhost/claude-code-skills-devops-dd5b61326148?source=rss----7ab1ae1562b8---4">Continue reading on Beyond Localhost »</a></p></div>]]></description>
            <link>https://medium.com/beyond-localhost/claude-code-skills-devops-dd5b61326148?source=rss----7ab1ae1562b8---4</link>
            <guid isPermaLink="false">https://medium.com/p/dd5b61326148</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[DevopsLesson]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 02:01:03 GMT</pubDate>
            <atom:updated>2026-10-27T12:00:39.346Z</atom:updated>
        </item>
    </channel>
</rss>