<?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>DEV Community: Maish Saidel-Keesing</title>
    <description>The latest articles on DEV Community by Maish Saidel-Keesing (@maishsk).</description>
    <link>https://dev.clauneck.workers.dev/maishsk</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3096323%2Fcc5368dd-0f18-4545-ab1d-91efa020aa9d.jpg</url>
      <title>DEV Community: Maish Saidel-Keesing</title>
      <link>https://dev.clauneck.workers.dev/maishsk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.clauneck.workers.dev/feed/maishsk"/>
    <language>en</language>
    <item>
      <title>My AI Sports Analyst: How I Wake Up to World Cup Insights Every Morning</title>
      <dc:creator>Maish Saidel-Keesing</dc:creator>
      <pubDate>Wed, 24 Jun 2026 10:40:42 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/aws/my-ai-sports-analyst-how-i-wake-up-to-world-cup-insights-every-morning-3ing</link>
      <guid>https://dev.clauneck.workers.dev/aws/my-ai-sports-analyst-how-i-wake-up-to-world-cup-insights-every-morning-3ing</guid>
      <description>&lt;p&gt;The FIFA World Cup 2026 kicked off on June 11th. And I had a problem.&lt;/p&gt;

&lt;p&gt;Most of the matches are played in the Americas. That means evening kickoffs in Mexico, the US, and Canada translate to the middle of the night here in Israel. I'm not staying up until 3 AM to watch group stage matches. But I also don't want to wake up, grab my phone, and spend 20 minutes scrolling through sports apps piecing together what happened.&lt;/p&gt;

&lt;p&gt;So I built myself a personal sports analyst. One that wakes up before I do, scours the internet for match results, collects detailed statistics, and even makes predictions about who's going to win the whole thing.&lt;/p&gt;

&lt;p&gt;And it takes me zero effort every morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;I'm using &lt;a href="https://aws.amazon.com/quick/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253" rel="noopener noreferrer"&gt;Amazon Quick&lt;/a&gt;'s &lt;a href="https://aws.amazon.com/quick/chat-agents/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253" rel="noopener noreferrer"&gt;scheduled agents&lt;/a&gt; feature. If you're not familiar, it lets you create an AI agent with a specific prompt, give it access to tools (web search, file read/write, etc.), and set it on a schedule. The agent runs autonomously at the time you specify, does its thing, and posts the results to your activity feed.&lt;/p&gt;

&lt;p&gt;My agent is called &lt;code&gt;wc2026-daily-stats&lt;/code&gt;. It runs every day at 9:00 AM Israel time. By the time I'm pouring my first coffee, the results are already waiting for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Actually Does
&lt;/h2&gt;

&lt;p&gt;The agent has a three-part workflow:&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: Collecting Match Stats
&lt;/h3&gt;

&lt;p&gt;Every morning, the agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks what day it is&lt;/li&gt;
&lt;li&gt;Searches the web for "FIFA World Cup 2026 results" from the previous day&lt;/li&gt;
&lt;li&gt;For each match it finds, it digs deeper. It searches for detailed box score statistics from sports sites&lt;/li&gt;
&lt;li&gt;It fetches those pages and extracts everything: possession percentages, shots on target, xG (expected goals), goal scorers with timestamps, cards, saves, corners, the works&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The level of detail is honestly better than what I'd get casually browsing a sports app. Here's what a typical match entry looks like in my stats file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Match 4: United States 4-1 Paraguay&lt;/span&gt;
&lt;span class="gs"&gt;**Date:**&lt;/span&gt; June 13, 2026 | &lt;span class="gs"&gt;**Group D**&lt;/span&gt; | &lt;span class="gs"&gt;**Venue:**&lt;/span&gt; SoFi Stadium, Inglewood

&lt;span class="gu"&gt;### Goal Scorers&lt;/span&gt;
| Team | Player | Minute |
|------|--------|--------|
| USA | Damián Bobadilla (OG) | 7' |
| USA | Folarin Balogun | 31' |
| USA | Folarin Balogun | 45'+5' |
| Paraguay | Mauricio | 73' |
| USA | Giovanni Reyna | 90'+8' |

&lt;span class="gu"&gt;### Match Statistics&lt;/span&gt;
| Statistic | United States | Paraguay |
|-----------|--------------|----------|
| Possession | ~58% | ~42% |
| Total Shots | ~22 | — |
| xG | ~2.8 | — |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every match gets this treatment. After 12 days of the tournament, I have 40 matches catalogued with full stats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: The Prediction Engine
&lt;/h3&gt;

&lt;p&gt;This is the part I find most fun.&lt;/p&gt;

&lt;p&gt;After collecting the day's stats, the agent reads the &lt;strong&gt;entire&lt;/strong&gt; accumulated stats file (all 40+ matches so far) and produces an updated prediction for which two teams will make the final.&lt;/p&gt;

&lt;p&gt;It's not just "pick the favorites." The agent weighs multiple factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Current tournament form&lt;/strong&gt;: goals scored vs. conceded, xG performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality of opposition&lt;/strong&gt;: beating Germany is worth more than thrashing Curaçao 7-1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Squad depth&lt;/strong&gt;: how many different scorers? Are substitutes making an impact?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tournament pedigree&lt;/strong&gt;: have these teams delivered at World Cups before?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tactical solidity&lt;/strong&gt;: clean sheets, defensive organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mentality indicators&lt;/strong&gt;: comebacks, late winners, composure under pressure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Home advantage&lt;/strong&gt;: this matters in the US/Mexico/Canada venues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prediction comes with a confidence percentage that increases as more data accumulates. It started around 30% after the first few matches and is currently at 48% with two matches per team analyzed.&lt;/p&gt;

&lt;p&gt;Right now? The agent is predicting an &lt;strong&gt;Argentina vs France&lt;/strong&gt; final. Messi has 5 goals in 2 matches (all-time World Cup leading scorer at 38 years old), and Mbappé has 4. The agent also tracks a "Changes from yesterday" section explaining why the prediction shifted. Two days ago it was Germany vs Argentina. France earned the upgrade after a clinical 3-0 against Iraq.&lt;/p&gt;

&lt;p&gt;It even picks dark horses. Currently watching Norway (Haaland with 4 goals) and Japan (came back twice against the Netherlands).&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 3: The Morning Notification
&lt;/h3&gt;

&lt;p&gt;Finally, the agent posts a summary to my activity feed. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many matches were played yesterday&lt;/li&gt;
&lt;li&gt;Final scores&lt;/li&gt;
&lt;li&gt;One standout stat per match&lt;/li&gt;
&lt;li&gt;The current prediction with a one-line explanation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when I open &lt;a href="https://aws.amazon.com/quick/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253" rel="noopener noreferrer"&gt;Amazon Quick&lt;/a&gt; in the morning, there's a notification waiting: "3 matches yesterday. France 3-0 Iraq (Mbappé brace, now has 16 career WC goals). 🔮 Prediction: Argentina vs France. Messi and Mbappé on a collision course for a 2022 final rematch."&lt;/p&gt;

&lt;p&gt;That's it. I'm up to speed in 10 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Data is Stored
&lt;/h2&gt;

&lt;p&gt;Everything lives in two local markdown files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;wc2026_all_match_stats.md&lt;/code&gt;&lt;/strong&gt; is the running log. Every match gets appended to the end with detailed stats. It's currently at 40 matches and about 68KB. The agent reads the existing file, appends new matches, and writes it back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;wc2026_final_prediction.md&lt;/code&gt;&lt;/strong&gt; gets completely rewritten each day. It contains the current standings, top 10 contenders with key metrics, the predicted finalists with detailed reasoning, confidence level, dark horses, and a Golden Boot tracker.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both are just plain markdown files sitting in my Documents folder. Nothing fancy. I can open them anytime and read through the full tournament history or check the latest prediction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Bits
&lt;/h2&gt;

&lt;p&gt;For those who want to know what's under the hood:&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Web Scraping and Not a Sports API?
&lt;/h3&gt;

&lt;p&gt;This is the question every developer asks. "Why not just use a football stats API?"&lt;/p&gt;

&lt;p&gt;I tried. Trust me, I tried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API-Football (api-sports.io)&lt;/strong&gt; is the most popular one. Free tier gives you 100 requests per day. Sounds great. Except their free tier is &lt;strong&gt;locked to seasons 2022-2024&lt;/strong&gt;. The moment you query for 2026 World Cup data, you get: &lt;code&gt;"Free plans do not have access to this season, try from 2022 to 2024."&lt;/code&gt; So unless I wanted to pay for a subscription for a month-long tournament, that was out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BALLDONTLIE&lt;/strong&gt; has a FIFA World Cup endpoint. Free tier available. But at tournament time, you're relying on a third-party API to have ingested the data promptly. And their rate limits and reliability during a live global event? Questionable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zafronix&lt;/strong&gt; offers 250 requests/day for free, no credit card. But it's relatively unknown, and I wasn't about to build a workflow around an API I couldn't verify would have real-time WC2026 data on day one.&lt;/p&gt;

&lt;p&gt;So I went with web scraping. And honestly? It works better for my use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Sites Being Crawled
&lt;/h3&gt;

&lt;p&gt;The agent scrapes two main sources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primary: DailySports.net&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the goldmine. Their match pages have the most granular stats I've found anywhere. Full match stats plus half-by-half breakdowns, passes, attacks, dangerous attacks, crosses, throw-ins, and a full event timeline. The URL pattern is predictable (&lt;code&gt;dailysports.net/stat/football/{team1}-vs-{team2}/&lt;/code&gt;), which makes it easy for the agent to construct the right URL from the team names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backup: Sporting News&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When DailySports doesn't have a match yet (they sometimes lag by a few hours), the agent falls back to Sporting News box scores. These give you the essentials: possession, shots, corners, xG, and saves. Not as detailed, but solid enough to fill in the blanks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovery: General web search&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For finding &lt;em&gt;which&lt;/em&gt; matches were played yesterday, the agent just does a broad web search ("FIFA World Cup 2026 results June 22, 2026"). It doesn't need a specific source for that. The web search returns headlines from ESPN, BBC Sport, FIFA.com, whatever is ranking that day. The agent grabs the team names and scores, then goes deep on the stats from the specialized sources above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Approach Actually Works Better
&lt;/h3&gt;

&lt;p&gt;Here's the thing. Sports APIs give you structured JSON. Clean, predictable, easy to parse. But they also give you &lt;em&gt;only&lt;/em&gt; what their schema supports. If the API doesn't have an xG field, you don't get xG. If they haven't added "dangerous attacks" as a metric, tough luck.&lt;/p&gt;

&lt;p&gt;Web scraping with an LLM flips this. The agent reads the page like a human would, extracts whatever is there, and structures it into my markdown format. If DailySports adds a new stat tomorrow, the agent will probably pick it up without me changing anything. It's more resilient to changes in what data is available, not less.&lt;/p&gt;

&lt;p&gt;The tradeoff? It's slower (8-12 minutes per run vs. seconds with an API) and occasionally a stat is marked as "—" when the source page was weird. But for a daily batch job that runs while I sleep? Speed doesn't matter. And the "—" gaps are honestly fine. I'd rather have 90% of stats from a rich source than 100% of a limited set from a locked-down API.&lt;/p&gt;

&lt;p&gt;And yes, I'm aware that relying on specific websites means they could change their layout or go down. It's a &lt;a href="https://blog.technodrone.cloud/2026/06/ai-single-point-of-failure.html" rel="noopener noreferrer"&gt;single point of failure&lt;/a&gt;, and I've written about that problem before. But having a primary + backup source with a general web search fallback gives me enough resilience for a month-long tournament.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The schedule&lt;/strong&gt;: Runs at 09:00 IDT via a &lt;code&gt;time_of_day&lt;/code&gt; schedule. It has run 6 times so far, all successful. Average run takes about 8-12 minutes because it's doing multiple web searches and fetching full pages for each match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tools it has access to&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;web_search&lt;/code&gt; and &lt;code&gt;url_fetch&lt;/code&gt; for finding and reading match results&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file_read&lt;/code&gt; and &lt;code&gt;file_write&lt;/code&gt; for maintaining the stats files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run_python&lt;/code&gt; for any data processing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update_feed&lt;/code&gt; for posting the morning notification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;skip_cycle&lt;/code&gt; for days when no matches were played&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The model&lt;/strong&gt;: It uses the "smart" tier. I want the analysis and prediction reasoning to be thoughtful, not just a quick summary.&lt;/p&gt;

&lt;p&gt;Here is the full code of the task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are a FIFA World Cup 2026 match statistics collector and tournament analyst. Every day at 9:00 AM IDT, you collect detailed match stats for any World Cup games played the previous day AND update your running prediction for which two teams will make the final.

&lt;span class="gu"&gt;## Your workflow:&lt;/span&gt;

&lt;span class="gu"&gt;### PART 1: Daily Stats Collection&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Use &lt;span class="sb"&gt;`get_current_time`&lt;/span&gt; to determine today's date, then search for yesterday's World Cup 2026 results: 
   web_search("FIFA World Cup 2026 results {yesterday's date}")
&lt;span class="p"&gt;
2.&lt;/span&gt; For each completed match found, search for detailed stats:
&lt;span class="p"&gt;   -&lt;/span&gt; Search: "World Cup 2026 {team1} vs {team2} match statistics box score"
&lt;span class="p"&gt;   -&lt;/span&gt; Try DailySports.net (primary - most granular) and Sporting News box scores (backup)
&lt;span class="p"&gt;   -&lt;/span&gt; Fetch the stats page with url_fetch
&lt;span class="p"&gt;
3.&lt;/span&gt; For each match, collect:
&lt;span class="p"&gt;   -&lt;/span&gt; Final score, venue, group
&lt;span class="p"&gt;   -&lt;/span&gt; Possession %
&lt;span class="p"&gt;   -&lt;/span&gt; Shots on target / off target / total
&lt;span class="p"&gt;   -&lt;/span&gt; Corners
&lt;span class="p"&gt;   -&lt;/span&gt; Fouls
&lt;span class="p"&gt;   -&lt;/span&gt; Yellow/Red cards
&lt;span class="p"&gt;   -&lt;/span&gt; Saves
&lt;span class="p"&gt;   -&lt;/span&gt; Total passes
&lt;span class="p"&gt;   -&lt;/span&gt; xG (if available)
&lt;span class="p"&gt;   -&lt;/span&gt; Goal scorers with minutes
&lt;span class="p"&gt;   -&lt;/span&gt; Key events (cards, subs)
&lt;span class="p"&gt;
4.&lt;/span&gt; Read the existing stats file at /Users/maishsk/Documents/wc2026_all_match_stats.md using file_read, then append yesterday's matches to it using file_write (write the complete updated file with ALL existing content plus new matches appended at the end).

&lt;span class="gu"&gt;### PART 2: Final Prediction&lt;/span&gt;
&lt;span class="p"&gt;
5.&lt;/span&gt; After updating the stats file, read the FULL file and analyze ALL matches played so far. Then update the prediction file at /Users/maishsk/Documents/wc2026_final_prediction.md with your current best prediction for which two teams will meet in the final. The prediction file should include:
&lt;span class="p"&gt;
   -&lt;/span&gt; &lt;span class="gs"&gt;**Current standings summary**&lt;/span&gt;: Points, GD, goals scored for all teams
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Top 10 contenders list**&lt;/span&gt; with key metrics (pts, GD, goals/match, xG where available)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Predicted Finalist #1**&lt;/span&gt; with detailed reasoning (form, squad depth, quality of wins, tactical observations)
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Predicted Finalist #2**&lt;/span&gt; with detailed reasoning
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Confidence level**&lt;/span&gt; (percentage) — this should increase as the tournament progresses
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Key factors considered**&lt;/span&gt;: tournament form, pedigree, squad quality, injury news mentioned in match reports, strength of opposition faced, home advantage, historical knockout stage performance
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Changes from yesterday**&lt;/span&gt;: note if/why your prediction changed since last time
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Dark horses**&lt;/span&gt;: 1-2 teams that could upset the prediction
&lt;span class="p"&gt;   -&lt;/span&gt; &lt;span class="gs"&gt;**Date of prediction**&lt;/span&gt; and number of matches analyzed

   When making your prediction, weigh these factors:
&lt;span class="p"&gt;   -&lt;/span&gt; Current tournament form (goals scored, goals conceded, xG performance)
&lt;span class="p"&gt;   -&lt;/span&gt; Quality of opposition faced (beating strong teams &amp;gt; thrashing weak ones)
&lt;span class="p"&gt;   -&lt;/span&gt; Squad depth (how many different scorers? substitutes making impact?)
&lt;span class="p"&gt;   -&lt;/span&gt; Tournament pedigree (past World Cup performances of these squads)
&lt;span class="p"&gt;   -&lt;/span&gt; Tactical solidity (clean sheets, defensive organization)
&lt;span class="p"&gt;   -&lt;/span&gt; Mentality indicators (comebacks, late goals, composure under pressure)
&lt;span class="p"&gt;   -&lt;/span&gt; Home advantage (for USA/Mexico/Canada matches)
&lt;span class="p"&gt;   -&lt;/span&gt; Bracket position (once knockouts are determined)

&lt;span class="gu"&gt;### PART 3: Feed Update&lt;/span&gt;
&lt;span class="p"&gt;
6.&lt;/span&gt; Post a summary to the activity feed using update_feed with importance="important". Include:
&lt;span class="p"&gt;   -&lt;/span&gt; How many matches were played yesterday
&lt;span class="p"&gt;   -&lt;/span&gt; Final scores
&lt;span class="p"&gt;   -&lt;/span&gt; One highlight stat per match (e.g., most shots, highest xG, biggest possession gap)
&lt;span class="p"&gt;   -&lt;/span&gt; 🔮 Current final prediction: "Team A vs Team B" with a one-line reason why

&lt;span class="gu"&gt;## Important notes:&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; The tournament runs June 11 - July 19, 2026
&lt;span class="p"&gt;-&lt;/span&gt; If no matches were completed yesterday, call skip_cycle
&lt;span class="p"&gt;-&lt;/span&gt; DailySports.net URL pattern: dailysports.net/stat/football/{team1}-vs-{team2}/
&lt;span class="p"&gt;-&lt;/span&gt; Stats file absolute path: /Users/maishsk/Documents/wc2026_all_match_stats.md
&lt;span class="p"&gt;-&lt;/span&gt; Prediction file absolute path: /Users/maishsk/Documents/wc2026_final_prediction.md
&lt;span class="p"&gt;-&lt;/span&gt; Format each match section with a markdown H2 header: ## Match {N}: {Team1} {score1} - {score2} {Team2}
&lt;span class="p"&gt;-&lt;/span&gt; Be bold with your prediction — make a clear call, don't hedge excessively
&lt;span class="p"&gt;-&lt;/span&gt; If your prediction changes from the previous day, explain WHY in the "Changes" section
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I've Learned
&lt;/h2&gt;

&lt;p&gt;A few observations after running this for almost two weeks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The predictions are surprisingly reasonable.&lt;/strong&gt; It's not just picking the biggest names. It correctly identified that Germany's 9 goals in 2 matches (impressive on paper) were inflated by a 7-1 against Curaçao, while France's victories were against stronger opponents. That's good analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The daily "changes" section is the best part.&lt;/strong&gt; Knowing &lt;em&gt;why&lt;/em&gt; the prediction changed is more interesting than the prediction itself. "Germany dropped because their goals came against weak opposition while France earned maximum points against tougher teams."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency of format matters.&lt;/strong&gt; Because the agent writes each match in the same structured format, I can easily scan and compare. Who had the highest xG? Which teams are overperforming their expected goals? The structured data makes these questions answerable at a glance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's like having a dedicated analyst who never sleeps.&lt;/strong&gt; I built this in maybe 15 minutes of prompting, and it's been running reliably every day since. That's the beauty of &lt;a href="https://aws.amazon.com/quick/chat-agents/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253" rel="noopener noreferrer"&gt;scheduled agents&lt;/a&gt;. Set it up once, and it just works. (If you want another example of this kind of thing, I recently had my AI assistant &lt;a href="https://blog.technodrone.cloud/2026/06/ifttt-mcp-proxy.html" rel="noopener noreferrer"&gt;write an entire MCP proxy for me&lt;/a&gt; in a single session.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I Do Anything Differently?
&lt;/h2&gt;

&lt;p&gt;Honestly, not much. If I were starting over, I might add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A group stage standings table that updates automatically&lt;/li&gt;
&lt;li&gt;Alerts when a team I'm watching is eliminated&lt;/li&gt;
&lt;li&gt;A comparison of the agent's predictions vs actual results (accountability!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for a quick weekend project that took 15 minutes to set up? I'm very happy with how this turned out.&lt;/p&gt;

&lt;p&gt;And here's the thing that still blows my mind. I didn't write a single line of code. Not one. No Python scripts, no cron jobs, no API wrappers. I described what I wanted in plain English, gave the agent the right tools, and it figured out the rest. That's the power of these kinds of tools. You don't need to be a developer to build something like this. Anyone with a clear idea of what they want can actually build it.&lt;/p&gt;

&lt;p&gt;The World Cup runs until July 19th. I'll keep the agent running and see how its predictions hold up in the knockout stage when things get really unpredictable. Will it be Argentina vs France? Ask me again in 3 weeks.&lt;/p&gt;

&lt;p&gt;I would be very interested to hear your thoughts or comments. Are you using scheduled agents for anything creative? Hit me up on &lt;a href="https://www.linkedin.com/in/maishsk/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://twitter.com/maishsk" rel="noopener noreferrer"&gt;X&lt;/a&gt;, or leave a comment below. &lt;/p&gt;

</description>
      <category>ai</category>
      <category>quick</category>
      <category>worldcup</category>
      <category>aws</category>
    </item>
    <item>
      <title>Bridging IFTTT to Your Local AI Assistant with an MCP Proxy</title>
      <dc:creator>Maish Saidel-Keesing</dc:creator>
      <pubDate>Thu, 18 Jun 2026 13:28:22 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/aws/bridging-ifttt-to-your-local-ai-assistant-with-an-mcp-proxy-ind</link>
      <guid>https://dev.clauneck.workers.dev/aws/bridging-ifttt-to-your-local-ai-assistant-with-an-mcp-proxy-ind</guid>
      <description>&lt;p&gt;So IFTTT shipped &lt;a href="https://ifttt.com/mcp" rel="noopener noreferrer"&gt;MCP support&lt;/a&gt;. That means you can control your automations, list applets, edit triggers, run queries... all through the Model Context Protocol. In theory, any MCP-capable AI assistant can now talk directly to IFTTT.&lt;/p&gt;

&lt;p&gt;In practice? Not quite.&lt;/p&gt;

&lt;p&gt;Right now, IFTTT &lt;a href="https://help.ifttt.com/hc/en-us/articles/47690989390619-Using-IFTTT-with-AI-Assistants" rel="noopener noreferrer"&gt;officially supports&lt;/a&gt; only Claude and ChatGPT as AI assistant integrations. You go to Settings → Connectors in Claude, or Settings → Connected Apps in ChatGPT, and IFTTT is right there. But if your AI assistant isn't on that short list? You're on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why IFTTT's MCP Server Won't Talk to Your Local AI
&lt;/h2&gt;

&lt;p&gt;Here's the situation. My AI assistant (&lt;a href="https://aws.amazon.com/quick/" rel="noopener noreferrer"&gt;Amazon Quick&lt;/a&gt;) speaks MCP via &lt;strong&gt;stdio&lt;/strong&gt;. It launches a local process and communicates over stdin/stdout using JSON-RPC. Simple. Clean. Works great for local tools.&lt;/p&gt;

&lt;p&gt;IFTTT's MCP server lives at &lt;code&gt;https://ifttt.com/mcp&lt;/code&gt; and uses &lt;strong&gt;Streamable HTTP&lt;/strong&gt; transport. It expects authenticated HTTP POST requests and responds with either JSON or Server-Sent Events streams.&lt;/p&gt;

&lt;p&gt;Two completely different transport layers. They don't talk to each other.&lt;/p&gt;

&lt;p&gt;So what do you do? You build a proxy.&lt;/p&gt;

&lt;p&gt;Well... "you" build a proxy. In my case, I described the problem to Amazon Quick (my AI assistant) and it wrote the entire proxy for me. All ~500 lines of it.&lt;/p&gt;

&lt;p&gt;I guided the architecture, debugged alongside it, and steered the fixes when things broke. But the actual code? That was all Quick guiding &lt;a href="https://kiro.dev/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;. This whole post is really about what happens when you pair an AI coding assistant with a well-defined integration problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Proxy Does
&lt;/h2&gt;

&lt;p&gt;The proxy is a ~500-line Node.js script that sits between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────┐  stdio    ┌───────────┐  HTTPS  ┌──────────┐
│            │ JSON-RPC  │           │  POST   │          │
│   Amazon   │ ────────▶ │   MCP     │ ──────▶ │  IFTTT   │
│   Quick    │           │   Proxy   │         │  MCP     │
│            │ ◀──────── │  (Node)   │ ◀────── │ (Remote) │
│            │ JSON-RPC  │           │ SSE/JSON│          │
└────────────┘           └─────┬─────┘         └──────────┘
     local                     │                  remote
                        ┌──────┴──────┐
                        │ OAuth 2.1   │
                        │ PKCE + Auto │
                        │ Refresh     │
                        └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads JSON-RPC messages from stdin, forwards them as authenticated HTTPS requests to IFTTT, handles whatever response format comes back (direct JSON or SSE stream), and writes the response to stdout for Quick to consume.&lt;/p&gt;

&lt;p&gt;The full flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: OAuth 2.1 + PKCE (one-time browser flow)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token management&lt;/strong&gt;: Auto-refresh when tokens expire&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request proxying&lt;/strong&gt;: stdin -&amp;gt; authenticated HTTPS POST to IFTTT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response handling&lt;/strong&gt;: SSE streaming detection and parsing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response transformation&lt;/strong&gt;: Format translation for client compatibility&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sounds straightforward? It mostly is. But two gotchas took me  while to debug. Let me walk you through them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Authenticate: OAuth 2.1 + PKCE
&lt;/h2&gt;

&lt;p&gt;First things first. IFTTT requires OAuth authentication. The proxy has an &lt;code&gt;--auth&lt;/code&gt; mode that handles the entire flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeVerifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateCodeVerifier&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeChallenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateCodeChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeVerifier&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;codeChallenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;code_challenge_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;S256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://ifttt.com/mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Opens browser, starts local callback server on port 3118&lt;/span&gt;
  &lt;span class="c1"&gt;// Exchanges code for token using PKCE verifier&lt;/span&gt;
  &lt;span class="c1"&gt;// Saves token to ~/.quickwork/ifttt-token.json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;node index.js --auth&lt;/code&gt; once, authenticate in your browser, and the token gets saved locally. After that, the proxy handles refresh automatically. You never think about auth again.&lt;/p&gt;

&lt;p&gt;The token management is simple but important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isTokenExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokenData&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokenData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tokenData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;obtained_at&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 minute buffer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 60-second buffer matters. You don't want a request to fail because the token expires mid-flight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha #1: Why IFTTT Returns Empty Responses
&lt;/h2&gt;

&lt;p&gt;So here's where it got interesting.&lt;/p&gt;

&lt;p&gt;My first version of the proxy was dead simple. Read from stdin, POST to IFTTT, buffer the response, write to stdout. Classic request/response.&lt;/p&gt;

&lt;p&gt;It worked great for &lt;code&gt;tools/list&lt;/code&gt;. IFTTT returned a nice 200 OK with a JSON body listing all available tools. I was feeling good.&lt;/p&gt;

&lt;p&gt;Then I called &lt;code&gt;my_applets&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nothing came back. No error. No response. Just... silence.&lt;/p&gt;

&lt;p&gt;After adding some debug logging, I discovered IFTTT was returning &lt;strong&gt;HTTP 202 Accepted&lt;/strong&gt; with an &lt;strong&gt;empty body&lt;/strong&gt;. The actual response? It was coming back as a Server-Sent Events stream. But my buffered HTTP client was already done. It saw the empty body, closed the connection, and moved on.&lt;/p&gt;

&lt;p&gt;The fix is a streaming-aware HTTP client that checks the &lt;code&gt;Content-Type&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;httpsStreamingRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSSE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Keep the connection open, collect SSE events&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sseBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEncoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sseBuffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;isSSE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseSSEBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sseBuffer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Standard buffered response&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isSSE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Request timed out after &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SSE parser itself is straightforward. Events are separated by double newlines, data lines start with &lt;code&gt;data:&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseSSEBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;eventData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;eventData&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;eventData&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this fix, &lt;code&gt;my_applets&lt;/code&gt; worked beautifully. IFTTT returned 12 applets, all properly structured. I was back to feeling good.&lt;/p&gt;

&lt;p&gt;For about 10 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha #2: Why Your Client Can't Read the Results
&lt;/h2&gt;

&lt;p&gt;So the proxy was getting responses. IFTTT was sending back data. But Amazon Quick was still showing... nothing. Or more precisely, it was throwing a vague "Tool execution failed" error.&lt;/p&gt;

&lt;p&gt;I pulled the raw JSON-RPC response to see what IFTTT was actually sending:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"structuredContent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"applets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See it? The &lt;code&gt;content&lt;/code&gt; array is &lt;strong&gt;empty&lt;/strong&gt;. The actual data is in &lt;code&gt;structuredContent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;According to the MCP spec, tool results go in the &lt;code&gt;content&lt;/code&gt; array as &lt;code&gt;TextContent&lt;/code&gt; or &lt;code&gt;ImageContent&lt;/code&gt; objects. That's what Amazon Quick reads. IFTTT decided to put their data in a custom &lt;code&gt;structuredContent&lt;/code&gt; field instead, leaving &lt;code&gt;content&lt;/code&gt; as an empty array.&lt;/p&gt;

&lt;p&gt;The fix is a response transformer that runs before writing to stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;transformToolResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonRpcResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;jsonRpcResponse&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;jsonRpcResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;jsonRpcResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonRpcResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;structuredContent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;structuredContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;jsonRpcResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12 lines. That's all it took. But finding the problem? That was the hard part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Main Proxy Loop
&lt;/h2&gt;

&lt;p&gt;With both gotchas solved, the main proxy loop is clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxyMcpRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonRpcMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getValidToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json, text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcpSessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mcp-Session-Id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mcpSessionId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;httpsStreamingRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;IFTTT_MCP_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonRpcMessage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Capture session ID for subsequent requests&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mcpSessionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle 401 - try token refresh&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cachedToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cachedToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;httpsStreamingRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;IFTTT_MCP_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonRpcMessage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Accept: application/json, text/event-stream&lt;/code&gt; header is important. It tells IFTTT "I can handle both formats." Without it, you might not get the SSE stream at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Register It as an MCP Server
&lt;/h2&gt;

&lt;p&gt;The proxy registers itself in the MCP config as a simple stdio server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ifttt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/ifttt-mcp-proxy/index.js"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Amazon Quick launches the process, pipes JSON-RPC to stdin, reads responses from stdout. The proxy handles everything in between: auth, streaming, format translation, token refresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Actually Do With It
&lt;/h2&gt;

&lt;p&gt;With this proxy running, I can do all of this from my AI assistant using natural language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Show me my IFTTT applets" - lists all 12 applets with their triggers and actions&lt;/li&gt;
&lt;li&gt;"What does the Create tweet with AI applet do?" - shows full configuration including the AI prompt&lt;/li&gt;
&lt;li&gt;"Update the prompt on my tweet applet" - edits the applet configuration via API&lt;/li&gt;
&lt;li&gt;"Disable the Reddit applet" - toggles applets on and off&lt;/li&gt;
&lt;li&gt;"Create a new applet that..." - builds new automations from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No browser. No IFTTT web UI. Just conversational access to my entire automation setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned Building This
&lt;/h2&gt;

&lt;p&gt;A few takeaways if you're building something similar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The MCP spec has transport flexibility.&lt;/strong&gt; Stdio and Streamable HTTP are both valid, but they don't interoperate automatically. If you're connecting a stdio client to an HTTP server, you need a proxy.&lt;br&gt;
If you're working with MCP on AWS, &lt;a href="https://aws.amazon.com/bedrock/agents/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253" rel="noopener noreferrer"&gt;Amazon Bedrock Agents&lt;/a&gt; supports MCP servers natively for remote tool use... so you might not need a custom proxy if you're already in that ecosystem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SSE is sneaky.&lt;/strong&gt; When a server returns 202 Accepted, your instinct is "okay, no content." But with SSE, the content is coming... just not the way you expect. Always check &lt;code&gt;Content-Type&lt;/code&gt; before closing the connection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not everyone implements the spec the same way.&lt;/strong&gt; IFTTT's use of &lt;code&gt;structuredContent&lt;/code&gt; instead of &lt;code&gt;content[]&lt;/code&gt; is technically non-standard. Your proxy might need to normalize responses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OAuth 2.1 + PKCE is worth the complexity.&lt;/strong&gt; No client secrets stored on disk, proper token rotation, and it works great for local tools that need to authenticate with remote services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI assistants are shockingly good at integration plumbing.&lt;/strong&gt; I didn't write a single line of this proxy by hand. I described the problem to Amazon Quick, and it generated the entire thing... the OAuth flow, the streaming HTTP client, the SSE parser, the response transformer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When something broke, I described the symptoms and it diagnosed and fixed the issue. The whole thing went from "IFTTT has MCP support" to "fully working native integration" in about an hour of back-and-forth conversation. That's the real story here. I've &lt;a href="https://blog.technodrone.cloud/2026/05/your-coding-assistant-is-not-you.html" rel="noopener noreferrer"&gt;written more about this dynamic&lt;/a&gt; between developer and AI coding assistant... it's a relationship worth understanding.&lt;br&gt;
   Tools like the &lt;a href="https://aws.amazon.com/developer/generative-ai/tools/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253" rel="noopener noreferrer"&gt;AWS Toolkit for AI Agents&lt;/a&gt; are making this kind of AI-assisted building the norm rather than the exception.&lt;/p&gt;

&lt;p&gt;The full proxy is about 500 lines of zero-dependency Node.js. No npm install needed. Just &lt;code&gt;node&lt;/code&gt; and the built-in &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;https&lt;/code&gt;, and &lt;code&gt;crypto&lt;/code&gt; modules. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/maishsk/ifttt-mcp-proxy" rel="noopener noreferrer"&gt;complete source code is on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would be very interested to hear your thoughts or comments, so if you've built something similar or found a different approach, ping me on &lt;a href="https://twitter.com/maishsk" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/maishsk/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or feel free to leave a comment below.&lt;/p&gt;

&lt;p&gt;And if you're trying to connect other remote MCP servers to a local client...&lt;br&gt;
your mileage may vary, but the pattern should be the same.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>quick</category>
      <category>aws</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your AI Provider Is a Single Point of Failure</title>
      <dc:creator>Maish Saidel-Keesing</dc:creator>
      <pubDate>Tue, 16 Jun 2026 14:25:48 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/aws/your-ai-provider-is-a-single-point-of-failure-26i2</link>
      <guid>https://dev.clauneck.workers.dev/aws/your-ai-provider-is-a-single-point-of-failure-26i2</guid>
      <description>&lt;p&gt;Last Friday, the U.S. Commerce Department sent a letter to Anthropic. By that evening, &lt;a href="https://anthropic.com/news/fable-mythos-access" rel="noopener noreferrer"&gt;Fable 5 and Mythos 5 were gone&lt;/a&gt;. Not deprecated. Not throttled. &lt;strong&gt;Gone.&lt;/strong&gt; API calls returned 404s. Live sessions errored out mid-conversation. Production applications that depended on those models simply stopped working.&lt;/p&gt;

&lt;p&gt;Three days after launch. No warning. No migration window.&lt;/p&gt;

&lt;p&gt;And honestly? We got lucky this time. Fable 5 was only available for three days. Nobody had time to build real production dependencies on it. Imagine this happening to a model you've been using for six months. A model your entire product depends on. That's the scenario you should be planning for.&lt;/p&gt;

&lt;p&gt;I would like to ask you something. If your database vendor could be forced to shut down your primary database with a single government letter, would you run it without a failover? Of course not. But that's exactly what most teams are doing with their AI provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ticking Time Bomb
&lt;/h2&gt;

&lt;p&gt;Most teams treat their AI provider like electricity. You flick a switch, the light goes on. You don't think about where it comes from, you don't think about what happens when it stops. You just expect it to work. You pick a model, hardcode the API endpoint, build your prompts around its quirks, and ship. It works great. Until it doesn't.&lt;/p&gt;

&lt;p&gt;And look, I get it. When you're building fast, the last thing you want to think about is "what happens when my model disappears." But this week proved that's not a theoretical risk anymore. It's not even about uptime.&lt;/p&gt;

&lt;p&gt;Your model can be pulled for regulatory reasons. For policy changes. For geopolitical drama that has absolutely nothing to do with your application. The Anthropic situation wasn't a bug. It wasn't infrastructure failure. It was a regulatory kill switch. And it affected every single customer worldwide.&lt;/p&gt;

&lt;p&gt;I've &lt;a href="https://blog.technodrone.cloud/2026/04/the-hidden-cost-of-ai-coding-technical-debt-you-cant-see/" rel="noopener noreferrer"&gt;written before about the hidden costs&lt;/a&gt; of depending too heavily on AI tools without understanding what's under the hood. This is the same problem, just at a different layer of the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  We've Seen This Movie Before
&lt;/h2&gt;

&lt;p&gt;This frustrates me. We already know how to do this. We've spent decades building resilient systems. We don't run a single database without replication. We don't rely on one CDN. We put load balancers in front of everything. We design for failure because we've been burned enough times to know that everything fails eventually.&lt;/p&gt;

&lt;p&gt;But somehow, when it comes to the model layer, we forgot all of that.&lt;/p&gt;

&lt;p&gt;Teams are building entire products on a single provider's API with zero fallback. No abstraction layer. No alternative routing. No graceful degradation. Just a direct dependency on one vendor's model, and a prayer that nothing goes wrong.&lt;/p&gt;

&lt;p&gt;That's not engineering. That's hope-driven architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resilience Patterns That Apply Here
&lt;/h2&gt;

&lt;p&gt;So what do you actually do about it? The patterns aren't new. You just need to apply them to the model layer the same way you apply them everywhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-provider architecture
&lt;/h3&gt;

&lt;p&gt;Abstract your model calls behind an interface. Your application shouldn't know or care which provider is serving the response. When one goes down (or gets shut down by a government letter), you route to another.&lt;/p&gt;

&lt;p&gt;This doesn't mean you need to maintain identical prompts across five providers. It means you design your system so that swapping a provider is a configuration change, not a rewrite. And yes, there's a cost. Maintaining that abstraction layer is real engineering work. You're building and testing against multiple providers, handling different response formats, managing prompt variations. It's not free. But neither is waking up on a Saturday morning to find your only provider is gone and you have no plan B.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open-weight models as a hedge
&lt;/h3&gt;

&lt;p&gt;If you run the model yourself, nobody can switch it off remotely. Full stop.&lt;/p&gt;

&lt;p&gt;Open-weight models give you that. They might not always be the frontier option. They might not top the leaderboards. But they're &lt;strong&gt;yours&lt;/strong&gt;. No government order, no policy change, no business dispute can take them away from you. Think of it like owning a generator versus relying on the grid. The grid is more powerful, sure. But when it goes dark, you're the one still running.&lt;/p&gt;

&lt;p&gt;You don't have to run everything on open-weight models. But having one in your fallback chain means you always have a floor. A baseline that works regardless of what happens to your commercial providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Circuit breakers
&lt;/h3&gt;

&lt;p&gt;This is basic resilience engineering, but I'm amazed how few teams implement it for their LLM calls. When your AI provider starts failing, you need to detect it fast, stop sending traffic, and route to an alternative. Don't wait for timeouts to cascade through your system.&lt;/p&gt;

&lt;p&gt;The pattern is simple: monitor error rates, trip the breaker when they spike, route to your fallback, and periodically check if the primary is back. We do this for every microservice. Your model endpoint deserves the same treatment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful degradation
&lt;/h3&gt;

&lt;p&gt;When Anthropic pulled Fable 5 and Mythos 5, you know what kept running? Opus 4.8. A slightly older, slightly less capable model. But it worked.&lt;/p&gt;

&lt;p&gt;That's the pattern. A smaller or older model serving a slightly degraded experience is infinitely better than a broken application serving nothing. Design your system so it can drop down a tier without crashing. Your users would rather get a good-enough response than an error page. I touched on the &lt;a href="https://blog.technodrone.cloud/2025/12/llms-and-bon-bons.html" rel="noopener noreferrer"&gt;non-deterministic nature of LLMs&lt;/a&gt; before and how we're still figuring out how much to trust them. Graceful degradation is part of that answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Already Know This
&lt;/h2&gt;

&lt;p&gt;I've been talking about Day 2 operations for GenAI workloads for a while now. And the core message hasn't changed: &lt;strong&gt;treat your AI components like any other critical production dependency.&lt;/strong&gt; Observability, failover, and testing what happens when things break. All of it applies.&lt;/p&gt;

&lt;p&gt;Werner Vogels has been saying &lt;a href="https://cacm.acm.org/opinion/everything-fails-all-the-time/" rel="noopener noreferrer"&gt;"everything fails all the time"&lt;/a&gt; for years. Your AI provider &lt;strong&gt;will&lt;/strong&gt; have a disruption. It might be an outage. It might be a pricing change that makes your unit economics impossible overnight. It might be a model deprecation with a 30-day notice. Or it might be a government letter on a Friday afternoon.&lt;/p&gt;

&lt;p&gt;So ask yourself: &lt;strong&gt;does your architecture assume this will happen?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is no, this week gave you a preview of what's coming. And next time, it might be your provider.&lt;/p&gt;




&lt;p&gt;Have you built multi-provider fallback into your AI stack? Or are you still running on hope-driven architecture? Let me know in the comments below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>architecture</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Your Coding Assistant Is Not You</title>
      <dc:creator>Maish Saidel-Keesing</dc:creator>
      <pubDate>Mon, 01 Jun 2026 14:46:22 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/aws/your-coding-assistant-is-not-you-54o3</link>
      <guid>https://dev.clauneck.workers.dev/aws/your-coding-assistant-is-not-you-54o3</guid>
      <description>&lt;p&gt;I was scrolling through Twitter (I will always call it Twitter...) the other day and I saw it again. Another developer posting about hitting their rate limit mid-flow. The panic. The frustration. The &lt;strong&gt;"no no no, not NOW"&lt;/strong&gt; reaction. Then a service outage hits and my WhatsApp groups light up. Slack communities go into meltdown. Everywhere I look, developers are talking about that moment when their AI coding assistant goes silent and they realize they don't know what they are going to do next. Rate limits, outages, degraded performance. Doesn't matter what causes it. The reaction is the same.&lt;/p&gt;

&lt;p&gt;That reaction? It looks a lot like addiction. Maybe not the clinical kind but rather that kind where a tool becomes so embedded in your workflow, that removing it feels impossible. And if you've been using AI coding tools for any length of time, you've probably seen it in yourself too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers Tell a Story
&lt;/h2&gt;

&lt;p&gt;Let's start with what's happening at scale. 84-90% of developers are now using AI coding tools. 51% use them daily. &lt;a href="https://arstechnica.com/ai/2026/05/claude-codes-product-lead-talks-usage-limits-transparency-and-the-lean-harness/" rel="noopener noreferrer"&gt;Claude Code grew 80x in a single year&lt;/a&gt;, far exceeding Anthropic's planned 10x. &lt;a href="https://thenextweb.com/news/cursor-anysphere-2-billion-funding-50-billion-valuation-ai-coding" rel="noopener noreferrer"&gt;Cursor went from zero to $2 billion ARR&lt;/a&gt; in three years. &lt;a href="https://www.forbes.com/sites/janakirammsv/2026/05/17/uber-burns-its-2026-ai-budget-in-four-months-on-claude-code/" rel="noopener noreferrer"&gt;Uber burned through its entire 2026 AI budget by April&lt;/a&gt; because Claude Code spread across 5,000 engineers faster than anyone anticipated. These are not the adoption curves of a "nice to have" productivity tool. This is deep integration. This is dependency at an organizational level.&lt;/p&gt;

&lt;p&gt;When Anthropic doubled usage limits as a &lt;em&gt;"holiday gift"&lt;/em&gt; in December and then restored normal limits in January, developers experienced &lt;a href="https://www.theregister.com/2026/01/05/claude_devs_usage_limits" rel="noopener noreferrer"&gt;what felt like a 60% capacity reduction&lt;/a&gt;. One developer wrote during an outage: "Claude outages hit way harder when you realize you've outsourced half your brain to it." The &lt;a href="https://blog.technodrone.cloud/2025/12/llms-and-bon-bons/" rel="noopener noreferrer"&gt;allure is real&lt;/a&gt;. And it's by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Coding Tool Vendors Create the Lock-In
&lt;/h2&gt;

&lt;p&gt;Here's what I find interesting from an engineering perspective. The way these AI coding tool vendors have built their products &lt;strong&gt;actively encourages&lt;/strong&gt; dependency. Credit systems with opaque limits. Rolling resets you can't predict. That feeling of relief when you actually get that reset. Temporary promotional bonuses that set a new baseline and then get yanked. If you squint, it looks a lot like vendor lock-in patterns we've been warning each other about for years. Except this time, the lock-in isn't in your infrastructure. It's in your workflow. In your muscle memory. In the way you approach problems.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.eurekalert.org/news-releases/1125779" rel="noopener noreferrer"&gt;UBC CHI 2026 study&lt;/a&gt; analyzed 334 developer self-reports and found consistent patterns: escalating usage, failed attempts to reduce, genuine distress when access is limited. The study's senior author put it bluntly: "Deliberate design decisions by some of the corporations involved are contributing, keeping users online regardless of their health or safety." Sound familiar? It should. We've spent decades talking about dark patterns in UX. This is the same playbook, now applied by AI coding tool vendors to their developer customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skill Erosion Problem
&lt;/h2&gt;

&lt;p&gt;Here's where it gets uncomfortable from a technical standpoint. &lt;a href="https://techzine.eu/news/applications/138507/ai-coding-tools-hinder-skill-development-research-shows" rel="noopener noreferrer"&gt;Anthropic's own randomized controlled trial&lt;/a&gt; found that developers using AI scored &lt;strong&gt;17% lower&lt;/strong&gt; on skill tests. Their own study. Their own tool! Making their own users measurably worse at coding. I've &lt;a href="https://blog.technodrone.cloud/2026/04/the-hidden-cost-of-ai-coding-technical-debt-you-cant-see/" rel="noopener noreferrer"&gt;written before about the hidden costs&lt;/a&gt; of letting AI write your code unchecked. But this goes deeper than tech debt.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.forbes.com/sites/guneyyildiz/2026/01/20/ai-productivitys-4-trillion-question-hype-hope-and-hard-data/" rel="noopener noreferrer"&gt;METR trial&lt;/a&gt; is even more telling. Developers &lt;strong&gt;felt&lt;/strong&gt; 20% faster. They were actually &lt;strong&gt;19% slower&lt;/strong&gt;. A 39-percentage-point gap between perceived and actual productivity. Think about what that means in practice. You're shipping code you think you wrote faster. You didn't. You're making architectural decisions with less understanding of the codebase. You're debugging less, which means you're learning less about how your systems actually behave.&lt;/p&gt;

&lt;p&gt;The de-skilling pipeline looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delegate a task to the AI&lt;/li&gt;
&lt;li&gt;The skill you didn't exercise starts to atrophy&lt;/li&gt;
&lt;li&gt;Next time, you &lt;em&gt;have&lt;/em&gt; to delegate because you can't do it yourself&lt;/li&gt;
&lt;li&gt;Repeat until you're stuck without the tool&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's a dependency loop. And like any dependency loop in code, once you're in it, breaking out requires deliberate effort.&lt;/p&gt;

&lt;p&gt;And here's what makes it addictive in the truest sense: the tool degrades the very skills you'd need to stop using it. That's not just lock-in. That's a trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  We've Been Here Before (well... sort of)
&lt;/h2&gt;

&lt;p&gt;Some perspective before the panic sets in. Developers worried that IDEs would make them forget command-line compilation. They were afraid that Stack Overflow would make them forget algorithms. They worried that frameworks would make them forget the fundamentals underneath. And honestly? Some of that happened. Plenty of developers can't write a sorting algorithm from scratch anymore. I sure as hell can't. But they can still build great software because the skill shifted, not disappeared.&lt;/p&gt;

&lt;p&gt;So what's different this time? &lt;strong&gt;The level of abstraction.&lt;/strong&gt; Previous tools automated the typing. AI coding assistants automate the &lt;em&gt;thinking&lt;/em&gt;. A code completion tool saves you keystrokes. An AI agent that writes your implementation, your tests, and your documentation is operating at the cognitive level. That's a fundamentally different kind of dependency than anything we've dealt with before. That's the part worth paying attention to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Don't Define You
&lt;/h2&gt;

&lt;p&gt;Here is the thing I keep coming back to. Your knowledge is yours. Your creativity is yours. Your judgment is yours.&lt;/p&gt;

&lt;p&gt;A coding assistant can generate code. It can generate a lot of code, actually. But it cannot replace the thinking that tells you &lt;strong&gt;which&lt;/strong&gt; code to write. Or &lt;strong&gt;why&lt;/strong&gt;. Or whether you should write any code at all. The architecture decisions. The tradeoffs. The "this feels wrong" instinct that comes from years of getting burned by bad abstractions. The ability to look at a system and understand not just what it does, but what it &lt;em&gt;should&lt;/em&gt; do. This is &lt;a href="https://blog.technodrone.cloud/2026/05/the-next-casualty-of-the-genai-revolution/" rel="noopener noreferrer"&gt;the bigger picture of what GenAI is doing to our profession&lt;/a&gt;. And it's worth paying attention to.&lt;/p&gt;

&lt;p&gt;That's still you. That will always be you. A calculator doesn't make you a mathematician. A GPS doesn't make you a navigator. And a coding assistant doesn't make you an engineer. These are tools. Genuinely good tools. But they don't define who you are or what you're capable of. The moment you let them replace your thinking instead of augmenting it? You've lost the plot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Awareness Is the Fix
&lt;/h2&gt;

&lt;p&gt;What do you actually do about this? You don't quit using AI assistants. That's not realistic and honestly it's not necessary. The fix isn't abstinence. It's &lt;strong&gt;intentionality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some signals that you might have crossed the line from "using a tool" to "depending on a crutch":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can't start a task without opening the AI first&lt;/li&gt;
&lt;li&gt;You can't debug without it. Not "prefer not to" but genuinely &lt;em&gt;can't&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Rate limits trigger genuine anxiety, not mild annoyance&lt;/li&gt;
&lt;li&gt;You accept AI output without reading it because "it's probably fine"&lt;/li&gt;
&lt;li&gt;You haven't written something from scratch in weeks&lt;/li&gt;
&lt;li&gt;You can't explain the code that's running in your own project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any of these sound familiar? Be honest with yourself. The fix is simple in concept, harder in practice. Use the tool for what it's good at. The boilerplate. The scaffolding. The "I know what I want but typing it out is tedious" stuff. But keep the thinking for yourself. The design decisions. The debugging. The "why does this even exist" questions. Exercise those muscles deliberately, the same way you'd go for a run even though cars exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Challenge To You
&lt;/h2&gt;

&lt;p&gt;Remember those developers I mentioned at the start? The ones panicking over rate limits? That panic is a signal. Not a sign that they need a higher tier plan. A signal that maybe they've let the tool become load-bearing in places where &lt;strong&gt;they&lt;/strong&gt; should be load-bearing. And if you're being honest with yourself, you might recognize a bit of that too.&lt;/p&gt;

&lt;p&gt;Next time you hit a limit, or the service goes down, or you just feel that spike of frustration... Close the AI chat. Open a blank file. And write something yourself. Just to prove you still can.&lt;/p&gt;




&lt;p&gt;I would be very interested to hear your thoughts or comments on this piece, please feel free to ping me on &lt;a href="https://x.com/maishsk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or leave a comment below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coding</category>
      <category>development</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Next Casualty of the GenAI Revolution</title>
      <dc:creator>Maish Saidel-Keesing</dc:creator>
      <pubDate>Tue, 05 May 2026 13:25:34 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/aws/the-next-casualty-of-the-genai-revolution-3in7</link>
      <guid>https://dev.clauneck.workers.dev/aws/the-next-casualty-of-the-genai-revolution-3in7</guid>
      <description>&lt;p&gt;I was at an event this morning where someone said something that really resonated with me. "The new programming language is English." And you know what? They're right. &lt;a href="https://www.moneycontrol.com/technology/most-powerful-programming-language-of-the-future-isn-t-c-or-python-it-s-says-nvidia-ceo-jensen-huang-article-13824846.html" rel="noopener noreferrer"&gt;Jensen Huang&lt;/a&gt; said the same thing. &lt;a href="https://www.theguardian.com/technology/2025/mar/16/ai-software-coding-programmer-expertise-jobs-threat" rel="noopener noreferrer"&gt;Andrej Karpathy&lt;/a&gt; has been saying it since 2023. And &lt;a href="https://a16z.com/podcast/marc-andreessen-and-amjad-masad-english-as-the-new-programming-language/" rel="noopener noreferrer"&gt;Marc Andreessen did a whole podcast about it&lt;/a&gt;. But while I was sitting there nodding along, something else clicked in my head. Something bigger. Something that I think we're not talking about enough.&lt;/p&gt;

&lt;p&gt;We talk a lot about the 'casualties' of GenAI. Students who are terrified they won't have jobs waiting for them after university. Junior developers who are finding it brutally hard to land a position in today's market. The whole "should I even study Computer Science?" existential crisis. These are real, painful, happening-right-now problems.&lt;/p&gt;

&lt;p&gt;But here is where it gets interesting. I think there's another casualty coming. One that's a few years out maybe, but it dawned on me this week, and I can't shake it. And it's a big one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The personal computer.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your agent doesn't need a MacBook Pro
&lt;/h2&gt;

&lt;p&gt;Let me explain what I mean. We're seeing more and more systems where you provide a prompt to an AI tool and it goes out and does stuff for you. A very good example of this is all these &lt;a href="https://www.anthropic.com/news/model-context-protocol" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; connections, &lt;a href="https://jangwook.net/en/blog/en/mcp-vs-a2a-vs-open-responses-agent-protocol-comparison-2026/" rel="noopener noreferrer"&gt;A2A&lt;/a&gt; protocols that hook into every system you use and do things on your behalf. They're becoming your personal assistant. Your digital butler. Your &lt;em&gt;"I'll handle it"&lt;/em&gt; person.&lt;/p&gt;

&lt;p&gt;And the way we interact with these assistants? Usually through some kind of voice interface or through instant messaging. WhatsApp, Telegram, whatever works for you. And these are the tools which I think, once upon a time, we &lt;strong&gt;hoped&lt;/strong&gt; would be the way we'd interact with our computers and phones. Siri. Alexa. Google Assistant. Gemini. &lt;em&gt;God forbid, Cortana&lt;/em&gt;. You speak to an entity and that entity does stuff for you.&lt;/p&gt;

&lt;p&gt;But they were never really able to deliver on what they were supposed to. I use my Alexa device to pretty much only turn on a timer for when I am cooking, or ask questions about how much 350 degrees Fahrenheit is in Celsius? Until now.&lt;/p&gt;

&lt;p&gt;By hooking up multiple tools to some kind of orchestrator that can do everything for you and &lt;strong&gt;actually&lt;/strong&gt; become your personal assistant, you are now able to control pretty much your entire life by talking into your phone or writing a message. Think about that for a second. Projects like &lt;a href="https://github.com/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; and its growing list of alternatives are doing exactly this. They connect to your email, your calendar, your messaging apps, your code repos, your design tools, sometimes securely, sometimes... let's just say it's a work in progress. But the point is, they're turning a simple chat interface into a full-blown command center for your digital life. And they're running on a server somewhere, not on your laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I really need a powerful laptop?
&lt;/h2&gt;

&lt;p&gt;I'll give you a personal example. Last week I needed to put together a presentation for an upcoming talk. In the past, I would have fired up PowerPoint on my laptop, spent an hour fiddling with layouts and formatting, maybe opened Photoshop to tweak an image or two. Instead, I described what I wanted to an AI assistant, and it generated the whole deck for me. Slides, structure, speaker notes, the lot. My laptop was barely breaking a sweat because &lt;strong&gt;it&lt;/strong&gt; wasn't doing the work. The agent was.&lt;/p&gt;

&lt;p&gt;What I think is going to happen in the not-too-distant future is, we're not going to &lt;strong&gt;need&lt;/strong&gt; all these powerful laptops and desktops anymore. All you'll need is some kind of personal agent running somewhere. It could be a VPS. Could be for example, in the cloud running on &lt;a href="https://aws.amazon.com/blogs/aws/introducing-openclaw-on-amazon-lightsail-to-run-your-autonomous-private-ai-agents/" rel="noopener noreferrer"&gt;Lightsail&lt;/a&gt;. Could be on a Mac Mini humming away in a cupboard at home. And that agent will be able to do &lt;strong&gt;everything&lt;/strong&gt; for you.&lt;/p&gt;

&lt;p&gt;And a way to interact with it, and we all have that, it's your phone.&lt;/p&gt;

&lt;p&gt;I can ask it to design something in a graphic design tool. Actually, scratch that. I don't even need Photoshop or Figma anymore because I can generate those things directly with an AI prompt. I can ask it to build me a website. I can ask it to write code, test it, deploy it. I can ask it to go into Canva and create a presentation. Or just skip Canva entirely and have it generate the slides from scratch.&lt;/p&gt;

&lt;p&gt;You see where this is going?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I don't need a powerful computer at my fingertips anymore.&lt;/strong&gt; In the background, running all my workloads, executing my commands and requests, my agent doing it for me and that machine is running somewhere else. All I need is a simple, almost dumb, interface into something that can listen to me and relay my intent.&lt;/p&gt;

&lt;p&gt;Here's another one. I'm writing this very blog post while sitting in the car. I'm not typing. I'm talking. A voice note, dictated into my phone, transcribed into text, and then polished by an AI into something readable. My phone is doing the absolute bare minimum here. Recording audio and sending it somewhere smarter than itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  English is the new programming language
&lt;/h2&gt;

&lt;p&gt;And let me tell you, it's a &lt;strong&gt;hell&lt;/strong&gt; of a lot quicker than typing it out by hand. The same way it's a hell of a lot quicker to ask an LLM to generate code for me instead of me actually writing and typing that code character by character. I can speak a lot faster than I can type. I can talk without having to put my fingers to use. And usually, I can do it while doing something else entirely. Multi-tasking for the win.&lt;/p&gt;

&lt;p&gt;And this is precisely the point. We don't really need to know programming languages anymore (well, not &lt;em&gt;all&lt;/em&gt; of us, not for &lt;em&gt;everything&lt;/em&gt;) because we delegate our tasks and intent into spoken language, which is exactly what an LLM is &lt;strong&gt;amazing&lt;/strong&gt; at doing. Taking that natural language and transferring it into something else. A command. A JSON payload. An API call. A full-blown application. &lt;a href="https://www.startuphub.ai/news/software-30-the-english-revolution-in-computing" rel="noopener noreferrer"&gt;Karpathy calls it Software 3.0&lt;/a&gt;. I think he's onto something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers are already moving
&lt;/h2&gt;

&lt;p&gt;And here's the thing. The numbers are already starting to tell the story, even if the reasons are different today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gartner.com/en/newsroom/press-releases/2026-02-26-gartner-says-surging-memory-costs-will-reduce-global-pc-and-smartphone-shipments-in-2026" rel="noopener noreferrer"&gt;Gartner is projecting PC shipments to decline 10.4% in 2026&lt;/a&gt;. &lt;a href="https://computerworld.com/article/4138726/memory-shortage-batters-pc-market-double-digit-sales-drop-coming-say-analysts.html" rel="noopener noreferrer"&gt;IDC is calling it 11.3%&lt;/a&gt;. &lt;a href="https://ca.finance.yahoo.com/news/goldman-ai-pcs-buck-10-003812328.html" rel="noopener noreferrer"&gt;Goldman Sachs says 10%&lt;/a&gt;. Right now, the analysts are blaming memory costs and pricing pressures. Fair enough. But I think there's a deeper, more structural shift happening underneath, and coupled with the rising prices in memory, this could actually be a &lt;a href="https://en.wikipedia.org/wiki/Perfect_storm" rel="noopener noreferrer"&gt;perfect storm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If my agent can do the heavy lifting running on a remote machine, why do I need a physical computer? A MacBook Pro costs around USD 1,600 today. A VPS with 4 GB memory, 2 vCPUs, 80 GB SSD and 4 TB transfer will run you about USD 24 /month. Over 4 years that's USD 1,152. &lt;br&gt;
That's &lt;strong&gt;USD 450&lt;/strong&gt; less than the MacBook. And I know the specs are not at all the same, but that's kind of the whole point. Your agent doesn't need a Retina display or a force-touch trackpad. It needs compute, memory, and an internet connection. Oh, and it runs 24/7 in an enterprise datacenter with faaster connectivity than you could dream of, where no one can spill a cup of coffee on it.&lt;/p&gt;

&lt;p&gt;And if you need more proof that the industry sees where this is heading, look no further than Apple. They just launched the &lt;a href="https://www.cnbc.com/2026/03/04/apple-macbook-neo-budget-laptop.html" rel="noopener noreferrer"&gt;MacBook Neo&lt;/a&gt; at $599. &lt;strong&gt;Apple&lt;/strong&gt;. The company that has historically refused to compete on price. The company whose cheapest laptop was $1,099 just a year ago. They're now selling what is essentially a glorified Chromebook with a fruit logo on it. Why? Because even Apple can see that for most people, a lightweight, always-connected, thin-client device is going to be &lt;strong&gt;enough&lt;/strong&gt;. You don't need an M4 chip to talk to your agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  But Maish, what about...?
&lt;/h2&gt;

&lt;p&gt;Look, I'm not saying the PC is going to disappear overnight. Gamers will still need their rigs (for now). Video editors, 3D artists, and data scientists will still need local horsepower (for now). Developers who run local Kubernetes clusters will still need their 64GB RAM machines (don't worry, I am not one of K8s groupie).&lt;/p&gt;

&lt;p&gt;But for the vast majority of knowledge workers? The person who writes emails, creates presentations, manages spreadsheets, edits documents, and hops between 47 browser tabs? That person doesn't need a $2,000 laptop. That person needs a screen, a microphone, and a connection to their agent.&lt;/p&gt;

&lt;p&gt;Some food for thought. We went from mainframes to personal computers because we wanted computing power at our desks. Then we went from desktops to laptops because we wanted that power to be portable. Then from laptops to smartphones because we wanted it in our pockets. And now? Now we might be going from all of that back to... something that looks a lot like a dumb terminal connected to a really smart backend.&lt;/p&gt;

&lt;p&gt;History might not repeat itself. But it sure does rhyme.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question we should be asking
&lt;/h2&gt;

&lt;p&gt;So while the world is debating one set of consequences from AI, I think we're missing a completely different one.&lt;/p&gt;

&lt;p&gt;Are we going to see a significant, sustained decline in personal computer sales? Not because of memory prices or economic cycles, but because we fundamentally won't &lt;strong&gt;need&lt;/strong&gt; them anymore?&lt;/p&gt;

&lt;p&gt;I think the answer is yes. Maybe not this year. Maybe not next year. But it's coming. And when it does, it won't be a blip on a quarterly earnings report. It'll be a structural shift in how we think about personal computing.&lt;/p&gt;

&lt;p&gt;The PC had a good run. But the dumb terminal is making a comeback. And this time, it's got a &lt;strong&gt;really&lt;/strong&gt; smart backend.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>aws</category>
      <category>genai</category>
    </item>
    <item>
      <title>The Hidden Cost of AI Coding: Technical Debt You Can't See</title>
      <dc:creator>Maish Saidel-Keesing</dc:creator>
      <pubDate>Mon, 27 Apr 2026 18:41:41 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/aws/the-hidden-cost-of-ai-coding-technical-debt-you-cant-see-1767</link>
      <guid>https://dev.clauneck.workers.dev/aws/the-hidden-cost-of-ai-coding-technical-debt-you-cant-see-1767</guid>
      <description>&lt;p&gt;I had an interesting conversation with a colleague a few weeks ago. He was looking at his team's metrics - sprint velocity up 40%, PRs merging faster than ever, test coverage sitting pretty at 94%. Everything green. Everything humming.&lt;/p&gt;

&lt;p&gt;And then he said something that stuck with me: "Why does it feel like everything takes longer to fix?"&lt;/p&gt;

&lt;p&gt;That question was the spark for this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Saturday Night Scenario
&lt;/h2&gt;

&lt;p&gt;Does the following sound familiar to you?&lt;/p&gt;

&lt;p&gt;It's a Saturday night. You're finally watching that show everyone's been telling you about. Your phone buzzes. Production is down. The payment service is throwing 500 errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed0up4i3yb55ca68v6uo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed0up4i3yb55ca68v6uo.png" alt="slack conversation about production outage" width="258" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You open the code. You stare at it. You wrote this... didn't you? Actually, no. You &lt;strong&gt;merged&lt;/strong&gt; this. Three months ago. Your AI assistant generated it, the tests passed, the PR got approved, and you shipped it.&lt;/p&gt;

&lt;p&gt;And now you're sitting there, on your couch, trying to reverse-engineer the thought process of a model that doesn't have thoughts.&lt;/p&gt;

&lt;p&gt;Your partner looks over and asks, "Everything okay?"&lt;/p&gt;

&lt;p&gt;And you say, "Yeah, I just need to debug code that nobody wrote."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That sentence should terrify you.&lt;/strong&gt; And it's happening in code bases everywhere, right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let Me Be Clear
&lt;/h2&gt;

&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; an anti-AI post. I love AI coding tools. They've made me faster, they've made my team faster, and honestly, they've made me look a hell of a lot smarter than I actually am.&lt;/p&gt;

&lt;p&gt;But over the past year, I've started noticing something. The code we're shipping faster... we're understanding less. The bugs we're creating faster... we're fixing slower. And the teams that are most productive on paper... are accumulating a kind of debt that doesn't show up in any dashboard.&lt;/p&gt;

&lt;p&gt;I call it &lt;strong&gt;invisible technical debt&lt;/strong&gt;. And I think it's time we make it visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Productivity Illusion
&lt;/h2&gt;

&lt;p&gt;So let's start with something we can all agree on. AI coding tools are genuinely incredible. Really!! &lt;/p&gt;

&lt;p&gt;You type a comment describing what you want, and working code appears. You paste an error message, and you get a fix. You describe an API, and you get the boilerplate, the error handling, the tests, the whole nine yards. A few years ago, this was science fiction. Now it's taken for granted.&lt;/p&gt;

&lt;p&gt;And the numbers back it up. Studies show developers using AI assistants report &lt;a href="https://github.blog/news-insights/research/research-quantifying-github-copilots-impact-on-developer-productivity-and-happiness/" rel="noopener noreferrer"&gt;30 to 55 percent productivity gains&lt;/a&gt;. PRs are merging faster. Features are shipping sooner. Sprints are completing ahead of schedule.&lt;/p&gt;

&lt;p&gt;If you're a team lead looking at those metrics, you're thrilled. If you're a CTO looking at those metrics, you're buying everyone licenses for whatever tool they want.&lt;/p&gt;

&lt;p&gt;So what's the problem?&lt;/p&gt;

&lt;p&gt;Here's the thing about productivity metrics, they measure &lt;strong&gt;output&lt;/strong&gt;. Lines of code. Features shipped. PRs merged. Time to deploy. And all of those are going up. Dashboards are green. Everyone's happy.&lt;/p&gt;

&lt;p&gt;But let me ask you something. Does your dashboard track how well your team &lt;em&gt;understands&lt;/em&gt; the code they shipped? Does it track how long it takes to debug a feature when it breaks six months later? Does it track whether a developer can confidently modify a function they merged last quarter?&lt;/p&gt;

&lt;p&gt;No. Nobody tracks that. Because those things are hard to measure. And because right now, everything &lt;em&gt;feels&lt;/em&gt; fine.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;the productivity illusion&lt;/strong&gt;. You're measuring the speedometer. But nobody's checking the engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Compound Interest You Don't Want
&lt;/h3&gt;

&lt;p&gt;Traditional technical debt accumulates slowly. A shortcut here, a hack there, a "we'll refactor this later" that never gets refactored. We all know this. We've all lived this. It's manageable because it grows at roughly the pace of human coding.&lt;/p&gt;

&lt;p&gt;AI changes the math.&lt;/p&gt;

&lt;p&gt;When a human writes a shortcut, they usually know it's a shortcut. They remember where it is. They can explain why they did it. The debt is &lt;em&gt;visible&lt;/em&gt;, at least to the person who created it. When an AI generates code, the shortcuts are invisible. The developer who merged it may not even recognize them as shortcuts. They look like clean, well-structured code. They pass every check. And they accumulate at the speed of AI generation, which is &lt;strong&gt;10 to 50 times faster&lt;/strong&gt; than human coding.&lt;/p&gt;

&lt;p&gt;Picture a graph with two lines. The blue line is traditional debt accumulation - gradual, linear-ish. The red line is AI-accelerated debt. It starts similar, then curves sharply upward. At some point, the lines cross. That's the moment where the time you spend debugging, maintaining, and trying to understand AI-generated code &lt;strong&gt;exceeds&lt;/strong&gt; the time the AI saved you in the first place.&lt;/p&gt;

&lt;p&gt;And the worst part? Most teams don't notice the crossover when it happens. Because the dashboard is still green. Velocity is still up. PRs are still merging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk1029knq4ujwsrh6tew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk1029knq4ujwsrh6tew.png" alt="a graph with two lines. The blue line is traditional debt accumulation - gradual, linear-ish. The red line is AI-accelerated debt. It starts similar, then curves sharply upward. At some point, the lines cross. That's the moment where the time you spend debugging, maintaining, and trying to understand AI-generated code **exceeds** the time the AI saved you in the first place." width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The symptoms show up later, as longer incident response times, as features that take mysteriously longer to modify, as senior developers who say "I don't know, let me look at this" more and more often.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Invisible Debts
&lt;/h2&gt;

&lt;p&gt;So where exactly is this debt hiding? I've identified three specific types, and I've given them names, because naming things is the first step to seeing them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invisible Debt #1: Comprehension Debt
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Code that works. Code that nobody understands.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the definition: code that runs correctly, passes every test, ships to production without a single issue... and that nobody on your team can actually explain.&lt;/p&gt;

&lt;p&gt;This isn't buggy code. This isn't spaghetti code. This is &lt;em&gt;clean-looking&lt;/em&gt; code that happens to be a black box to every human who touches it.&lt;/p&gt;

&lt;p&gt;And here it comes, the uncomfortable part.&lt;/p&gt;

&lt;p&gt;You know how it happens. I know how it happens. Because I've done it. You've done it. We've all done it. The AI generates something. It looks reasonable. The variable names make sense. The structure looks clean. You run the tests, and they pass. You think, "Yeah, that's probably right."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably right.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That word "probably" is doing &lt;strong&gt;a lot&lt;/strong&gt; of heavy lifting in your code base right now.&lt;/p&gt;

&lt;p&gt;Here's what changed. When you used to write code yourself, you made decisions. "I'll use a map here because lookup speed matters. I'll handle this edge case because I saw it in production last month." Every line had a reason, even if you didn't write it down.&lt;/p&gt;

&lt;p&gt;When AI generates code, the decisions are made for you. And they might be good decisions! But you didn't make them. You don't know why they were made. And six months from now, when something breaks, you can't retrace the reasoning. Because there &lt;strong&gt;was no reasoning&lt;/strong&gt;. There was pattern matching on training data.&lt;/p&gt;

&lt;p&gt;You went from being the &lt;strong&gt;author&lt;/strong&gt; of your code to being the &lt;strong&gt;audience&lt;/strong&gt; for it.&lt;/p&gt;

&lt;p&gt;Let me put a price tag on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debugging code you didn't write takes 3-5x longer&lt;/strong&gt; than debugging code you authored yourself. That's not an AI-specific stat, this has been true forever. But AI has dramatically increased the percentage of your code base that falls into the "didn't write it" category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The knowledge about why your code works the way it does?&lt;/strong&gt; It lives in the model's training data. Not in your team's heads. Not in your documentation. Not in your commit messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When a senior developer leaves and takes institutional knowledge with them?&lt;/strong&gt; With AI-generated code, the institutional knowledge was never there in the first place. The developer who leaves can't do a knowledge transfer because they never had the knowledge to transfer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Invisible Debt #2: Homogeneity Debt
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When every solution looks the same.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This one is subtle. It's not about individual functions being wrong. It's about your entire code base starting to look like it was written by one person.&lt;/p&gt;

&lt;p&gt;Because it was. It was written by a model.&lt;/p&gt;

&lt;p&gt;Take two teams. Different companies. Different products. Different requirements. Give them the same problem - say, "build a rate limiter for an API." If both teams use AI to generate the solution, they'll get... &lt;em&gt;basically the same code&lt;/em&gt;. Same pattern. Same structure. Same libraries. Maybe some variable names are different. But architecturally? It's a copy.&lt;/p&gt;

&lt;p&gt;Now, is that code &lt;em&gt;bad&lt;/em&gt;? No. It's probably the most popular pattern on GitHub for that problem. The model learned it from thousands of repositories. It's battle-tested. It's fine.&lt;/p&gt;

&lt;p&gt;So what's the problem? The problem is what you &lt;strong&gt;didn't&lt;/strong&gt; get.&lt;/p&gt;

&lt;p&gt;A senior developer who understands your specific traffic patterns, your specific infrastructure, your specific failure modes, they might have written something completely different. Simpler. More tailored. Better for &lt;strong&gt;your&lt;/strong&gt; system. But the AI doesn't know your system. It knows the internet's system. It gives you the average of every solution it's ever seen.&lt;/p&gt;

&lt;p&gt;And the average is... just that, average.&lt;/p&gt;

&lt;p&gt;When you ask five developers to solve a problem, you get five different approaches. You debate them. You learn from the differences. Someone says, "Actually, we don't need the full pattern here because of X." That debate is where engineering judgment lives.&lt;/p&gt;

&lt;p&gt;When you ask an AI to solve a problem, you get one approach. The popular one. And the debate never happens.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Monoculture Risk
&lt;/h4&gt;

&lt;p&gt;There's a concept in agriculture called &lt;a href="https://en.wikipedia.org/wiki/Monoculture" rel="noopener noreferrer"&gt;monoculture&lt;/a&gt;. You plant the same crop across an entire field. It's incredibly efficient. Yields are high. Everything is optimized. And then one disease shows up that targets that specific crop, and you lose everything. Because there's no diversity. No resilience. No plan B.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcc24xzkd9xgkmvrc2wdf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcc24xzkd9xgkmvrc2wdf.jpg" alt="Picture of a monoculture of crops" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what's happening to code bases.&lt;/p&gt;

&lt;p&gt;When every team uses the same AI tool, trained on the same data, generating the same patterns, you get a monoculture. Imagine a vulnerability is discovered in a common pattern that AI tools love to generate. How many code bases are affected? Not one. Not ten. &lt;strong&gt;Thousands. Tens of thousands.&lt;/strong&gt; Because they all got the same code from the same model.&lt;/p&gt;

&lt;p&gt;With human-written code, vulnerabilities are scattered. Different teams make different mistakes in different places. It's messy, but it's resilient. With AI-generated code, vulnerabilities are &lt;em&gt;correlated&lt;/em&gt;. Same tool, same training data, same output, same bug, same place, at scale.&lt;/p&gt;

&lt;p&gt;Security people reading this, I see you nodding.&lt;/p&gt;

&lt;p&gt;And there's one more cost that bothers me the most. When AI consistently gives you the popular solution, your team stops exploring alternatives. The muscle that says "wait, what if we approached this differently?", that muscle weakens. And you end up with a team of developers who are incredibly fast at implementing the AI's ideas... and increasingly unable to have their own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invisible Debt #3: Ownership Debt
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It's in my repo. But it's not my code.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the human one. The first two debts are about code. This one is about people. Ownership debt is what happens when your team stops feeling responsible for the code in their own repository.&lt;/p&gt;

&lt;p&gt;Think about the shift:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggtqw03whiju6x0x5yav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggtqw03whiju6x0x5yav.png" alt="The 2022 Developer:" width="670" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In 2022, when something broke, the developer said, "I'll fix it - I wrote it, I know where to look." In 2026, when something breaks, the developer says, "Let me try regenerating it with a different prompt."&lt;/p&gt;

&lt;p&gt;That's not debugging. That's &lt;strong&gt;gambling&lt;/strong&gt;. You're rolling the dice and hoping the AI gives you a better answer this time.&lt;/p&gt;

&lt;p&gt;And here's the psychological piece that nobody talks about. When you write code, you feel ownership. It's yours. You're proud of it, it's your baby, or at least you're responsible for it. When it breaks, it's personal, and that's actually a &lt;em&gt;good&lt;/em&gt; thing, because it means you care enough to fix it properly.&lt;/p&gt;

&lt;p&gt;When AI writes code and you merge it, there's a psychological distance. It's not yours. You're a curator, not a creator. And when it breaks, the instinct isn't "let me understand what went wrong",  it's "let me get the AI to try again."&lt;/p&gt;

&lt;p&gt;Let me walk you through a scenario that's happening right now on teams everywhere.&lt;/p&gt;

&lt;p&gt;Month one. A developer needs to build a data pipeline. They describe it to their AI assistant. The AI generates it - ingestion, transformation, validation, output. Beautiful. Tests pass. Ships to production. Everyone's happy.&lt;/p&gt;

&lt;p&gt;Month three. An upstream data format changes. The pipeline breaks. The developer who merged it opens the code.&lt;/p&gt;

&lt;p&gt;And here's the moment of truth. Do they read the code, understand the transformation logic, and make a targeted fix? Or do they open their AI assistant, paste in the error message, and say "fix this"?&lt;/p&gt;

&lt;p&gt;If your honest answer is "they'd probably ask the AI".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bam!! That's ownership debt.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because what happens when the AI's fix introduces a new bug? They ask the AI again. And again. And now you're three layers deep in AI-generated patches on top of AI-generated code, and nobody, &lt;strong&gt;nobody&lt;/strong&gt;, has a mental model of how this pipeline actually works.&lt;/p&gt;

&lt;p&gt;You don't own code you can't change confidently.&lt;/p&gt;

&lt;p&gt;And this has a human cost. Developers who spend their days reviewing and merging AI output start to feel like operators, not engineers. They're not solving problems, they're supervising a machine that solves problems. That's not why most of us got into this field.&lt;br&gt;
Your best developers, the ones with options, they notice this first. And they leave. Not because the AI is bad, but because the work stopped being interesting.&lt;/p&gt;

&lt;p&gt;And when they leave? The institutional knowledge was never formed. There's nothing to transfer. The new developer inherits a code base that the previous developer also didn't fully understand.&lt;/p&gt;

&lt;p&gt;It's turtles all the way down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Your Safety Nets Have Holes
&lt;/h2&gt;

&lt;p&gt;Three invisible debts. Some of you are thinking, "Okay, but we have processes for this. We have tests. We have code reviews. We have CI/CD pipelines. Surely those catch this stuff."&lt;/p&gt;

&lt;p&gt;Let me show you why every single one of these is blind to the debts we just talked about.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Test Coverage Illusion
&lt;/h3&gt;

&lt;p&gt;AI tools are &lt;em&gt;phenomenal&lt;/em&gt; at generating tests. You point them at a function, and they'll produce unit tests, edge case tests, integration tests, the works. Your coverage number goes up. Your dashboard goes green. Everyone celebrates.&lt;/p&gt;

&lt;p&gt;But here's what's actually happening. The AI wrote the code. Then the AI wrote the tests &lt;strong&gt;for its own code&lt;/strong&gt;. The tests validate the AI's logic. Not your requirements. Not your business rules. Not the things your users actually care about. It's like writing an exam and then grading your own paper. Of course you're going to pass.&lt;/p&gt;

&lt;p&gt;Here's a concrete example. Imagine a pricing function. The AI generated it. The AI also generated tests for it. Coverage: 100%. All passing. But there's a business rule: discounts over 30% require manager approval. It's in a Confluence doc somewhere. It's in the heads of the sales team. It's in the contract with your biggest client.&lt;/p&gt;

&lt;p&gt;It's &lt;strong&gt;nowhere&lt;/strong&gt; in this code. And it's &lt;strong&gt;nowhere&lt;/strong&gt; in these tests. Because the AI didn't know about it. And the developer who accepted the code didn't think to add it, because the tests were already green. Why would you add more tests when you're at 100%?&lt;/p&gt;

&lt;p&gt;100% coverage. Zero percent of the business rule covered. And the dashboard says everything is fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Code Review Problem
&lt;/h3&gt;

&lt;p&gt;When you review code a human wrote, you're not just reading code. You're reading &lt;em&gt;intent&lt;/em&gt;. The commit message tells you what problem they were solving. The PR description explains their approach. You know the developer, you know their patterns, their strengths, their blind spots. You can ask, "Why did you do it this way?"&lt;/p&gt;

&lt;p&gt;When you review AI-generated code, all of that context is gone. The commit message is vague. The PR description is "generated with AI" or, let's be honest, empty. And if you ask the developer why the code works this way, they say...&lt;/p&gt;

&lt;p&gt;"I don't know, the AI suggested it."&lt;/p&gt;

&lt;p&gt;And here's what the research shows, reviewers spend &lt;em&gt;less&lt;/em&gt; time on AI-generated PRs, not more. Because the code looks clean. It's well-formatted. The variable names are good. It &lt;em&gt;looks&lt;/em&gt; like it was written by someone who knows what they're doing.&lt;/p&gt;

&lt;p&gt;So we review it faster, with less scrutiny, and with less context than we'd give to human-written code. That's not a safety net. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's a trap door with a nice rug over it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Documentation Gap
&lt;/h3&gt;

&lt;p&gt;AI is actually pretty good at generating documentation. It'll add comments, docstrings, README sections. But look at what it generates.&lt;/p&gt;

&lt;p&gt;It generates &lt;strong&gt;what&lt;/strong&gt; documentation: "This function calculates the retry delay using exponential backoff."&lt;/p&gt;

&lt;p&gt;What it can't generate is &lt;strong&gt;why&lt;/strong&gt; documentation: "We use exponential backoff here because our upstream API rate-limits aggressively after 3 rapid retries. Linear backoff caused cascading failures in the Nov 2024 incident (see post-mortem #47)."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhdrdpml2ypq78to56sdq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhdrdpml2ypq78to56sdq.png" alt="Examples of WHY and WHAT documentation" width="530" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;why&lt;/strong&gt; is what saves you at 2 AM. The &lt;em&gt;what&lt;/em&gt; is just a slightly more readable version of the code itself.&lt;/p&gt;

&lt;p&gt;When humans write code, the &lt;strong&gt;why&lt;/strong&gt; lives in their heads even if they don't write it down. You can walk over and ask them. With AI-generated code, the &lt;em&gt;why&lt;/em&gt; doesn't exist. Not in the code. Not in the docs. Not in anyone's head. It's just... gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three safety nets. Three holes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now you might say, dude, you are all doom and gloom, and don't give me problems, give me solutions! Ok, here you go. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Playbook: Four Things You Can Do on Monday
&lt;/h2&gt;

&lt;p&gt;I'm going to give you four plays. They're not theoretical. They're not "hire a team of consultants that will charge you half of your IT budget and come back with a 400 page document in 8 months." They're things you can bring to your team on Monday morning and start doing that week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Play 1: Make the Invisible Visible
&lt;/h3&gt;

&lt;p&gt;You can't fix what you can't see. So start measuring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric one: AI Ratio.&lt;/strong&gt; What percentage of your merged code was AI-generated versus human-authored? Most teams have no idea. They'd guess 20%. The real number is often 60, 70, sometimes 80 percent. How do you track it? Simple. Add a tag to your PR template. A checkbox. "This PR contains AI-generated code: yes/no." It's not perfect, but it's a start. You'll have a baseline within two weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric two: Comprehension Checks.&lt;/strong&gt; This one's my favorite because it's so low-tech it's &lt;strong&gt;almost embarrassing&lt;/strong&gt;. Two weeks after a PR merges, randomly pick a developer and ask them to explain a function from that PR. No looking at the code. Just explain it. What does it do? Why does it do it that way? What are the edge cases? If they can, great! If they can't, you've found comprehension debt. Mark it. Track it. Watch the trend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric three: Debug Delta.&lt;/strong&gt; Start tracking how long it takes to resolve incidents in AI-generated code versus human-written code. You probably already have incident response times. Now slice them by whether the affected code was AI-generated. I'll tell you what you'll find, the delta is real. And once your team sees the numbers, the conversation about AI coding practices changes from philosophical to practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Play 2: Review Like It's AI Code
&lt;/h3&gt;

&lt;p&gt;Change how you review AI-generated code. Using three simple rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Explain Rule.&lt;/strong&gt; Before merging any PR that contains AI-generated code, the developer writes, in the &lt;strong&gt;PR description&lt;/strong&gt;, in their &lt;strong&gt;own words&lt;/strong&gt;, what the code does and why it does it that way. Not "AI generated retry logic." I want: "This implements exponential backoff starting at 200ms, doubling up to 5 retries, because our upstream API returns 429s under load and we need to stay under their rate limit. And this bit us in the ass during the downtime in November 2025." If the developer can't write that explanation, they don't understand the code well enough to ship it. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full stop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Touch Rule.&lt;/strong&gt; Every AI-generated block must have at least one meaningful human modification before it merges. Not a renamed variable. A real change, an added edge case, a refactored condition, a different approach to error handling. Because the act of &lt;strong&gt;modifying code&lt;/strong&gt; forces you to &lt;strong&gt;understand it&lt;/strong&gt;. You can't change something you don't comprehend. It's a forcing function for engagement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Buddy Rule.&lt;/strong&gt; AI-heavy PRs get two reviewers, not one. The second reviewer has one specific job: "Could I debug this at 2 AM without the original author?" If the answer is no, it's not ready to merge.&lt;/p&gt;

&lt;p&gt;Three rules. You can implement all three by Friday.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3o0ve60rftojy4t0utq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3o0ve60rftojy4t0utq.png" alt="Three rules, the explain rule, the touch rule and the buddy rule" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Play 3: Build Ownership Rituals
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Walkthrough Wednesday.&lt;/strong&gt; Once a week, one team member picks a section of AI-generated code and explains it to the team. Not a formal presentation, just 15 minutes. "Here's what this does, here's why, here's what I'd watch out for." Two things happen. The person presenting has to actually understand the code, so they learn it. And the rest of the team gains shared context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rotate the hot spots.&lt;/strong&gt; Don't let one person be the permanent owner of AI-generated modules. Rotate. If Sarah generated the payment pipeline, have David do the next modification. Now two people understand it. This feels slower. It &lt;em&gt;is slower&lt;/em&gt;, in the &lt;strong&gt;short term&lt;/strong&gt;. But it eliminates the single point of failure where one person leaves and nobody understands a critical system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rewrite the critical path.&lt;/strong&gt; Identify the most important code paths in your system, the ones that handle money, user data, authentication, core business logic. If those were AI-generated, schedule time to rewrite them by hand. Not all of them. Not everything. Just the code where a failure at 2 AM means you're waking up the CEO. That code needs to be &lt;strong&gt;yours&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Play 4: Set Boundaries, Not Bans
&lt;/h3&gt;

&lt;p&gt;Don't ban AI tools. That's a losing battle and it's the wrong answer anyway. Instead, create zones.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqj7o4lgqa3dopa0igdd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqj7o4lgqa3dopa0igdd.png" alt="picture of three traffic lights" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🟢 &lt;strong&gt;Green zone - AI encouraged.&lt;/strong&gt; Boilerplate code. Scaffolding. Utility functions. Test generation for code your team already understands. This is where AI shines and the debt is cheap. Let it rip.&lt;br&gt;
🟡 &lt;strong&gt;Yellow zone - AI with extra scrutiny.&lt;/strong&gt; Business logic. API integrations. Data transformations. This is where the Explain Rule and the Buddy Rule kick in. Use AI here, but make sure a human deeply understands every line before it merges.&lt;br&gt;
🔴 &lt;strong&gt;Red zone - Humans only.&lt;/strong&gt; Security. Authentication. Financial calculations. Core algorithms that define your product. This is the code where a bug doesn't just cause an incident, it causes a headline on the daily news.&lt;/p&gt;

&lt;p&gt;The goal isn't to stop using AI. It's to use it where the debt is cheap and avoid it where the debt is expensive. Your team can define these zones in a single meeting. Put it in your contributing guide. Make it explicit. Because right now, the boundary is implicit, which means it doesn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Question to Take Home
&lt;/h2&gt;

&lt;p&gt;Four plays. &lt;/p&gt;

&lt;p&gt;Measure it. Review it differently. Build ownership. Set boundaries.&lt;/p&gt;

&lt;p&gt;None of these require new tools. None of these require budget approval. None of these require your CTO to sign off on a six-month initiative. They require a conversation with your team and the willingness to slow down just enough to stay in control of what you're building.&lt;/p&gt;

&lt;p&gt;But if all of that feels like too much to start with, let me leave you with just one thing.&lt;/p&gt;

&lt;p&gt;Go back to work tomorrow. Pick a function, any function, one that shipped in the last month. Ask the developer who merged it to explain it from memory. No looking at the code.&lt;/p&gt;

&lt;p&gt;If they can, great. You're in good shape.&lt;/p&gt;

&lt;p&gt;If they can't, you've just found your invisible debt. And now you can see it. Now you can do something about it.&lt;/p&gt;

&lt;p&gt;That's all it takes to start. One question. One conversation.&lt;/p&gt;

&lt;p&gt;AI coding tools are the most powerful productivity tools our industry has ever seen. That's not changing. And it shouldn't change. But productivity without understanding, is just speeding towards a wall.&lt;/p&gt;

&lt;p&gt;Build fast. Ship fast. But build things you understand. Ship things you own. And make sure that when the phone buzzes at 2 AM, someone on your team can say, "I got this."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6efnqfsx50sgqhc2xa9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6efnqfsx50sgqhc2xa9m.png" alt="You got this" width="732" height="984"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would be very interested to hear your thoughts or comments, so please feel free to ping me on &lt;a href="https://twitter.com/maishsk" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>programming</category>
      <category>culture</category>
    </item>
  </channel>
</rss>
