<?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: Kiell Tampubolon</title>
    <description>The latest articles on DEV Community by Kiell Tampubolon (@kielltampubolon).</description>
    <link>https://dev.to/kielltampubolon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3890870%2Ff4c1760b-670f-4d29-b0a8-29dc39842afa.jpg</url>
      <title>DEV Community: Kiell Tampubolon</title>
      <link>https://dev.to/kielltampubolon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kielltampubolon"/>
    <language>en</language>
    <item>
      <title>I Struggled with Data Analysis. Claude for Excel Changed Everything.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Fri, 08 May 2026 12:02:15 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-struggled-with-data-analysis-claude-for-excel-changed-everything-24o0</link>
      <guid>https://dev.to/kielltampubolon/i-struggled-with-data-analysis-claude-for-excel-changed-everything-24o0</guid>
      <description>&lt;p&gt;At 2 AM, I was ready to throw my laptop out the window. I had just spent hours gaining zero insights from a sprawling sea of data. Then I stumbled upon Claude for Excel, and suddenly everything clicked into place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Real Error or Messy Code
&lt;/h2&gt;

&lt;p&gt;Like many developers, especially those of us who aren't formally trained as data analysts, Excel can sometimes feel like a jungle. I was trying to generate reports that needed to highlight sales trends, which required me to wrangle all that data into a coherent narrative. I needed to create pivot tables to analyze the responses, but navigating through cumbersome formulas was turning my brain into mush. I spent hours merging cells, applying filters, and trying to remember whether I used SUM or AVERAGE last time.&lt;/p&gt;

&lt;p&gt;One day, I spent an entire Sunday trying to get a complex formula to work. I ended up with #VALUE! errors splattered across my sheet, leading to frustration that lasted longer than I care to admit. My family could see my stress levels rising as I muttered incoherently about Excel's cryptic syntax. I wanted to blame the software, but the truth was, I was the one wrestling with messy data and unrealistic expectations on myself. The more I struggled, the more irrational I became.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Enter Claude for Excel—a tool that instantly began to take the burden off my shoulders. After a brief introduction and tutorial, I realized this could be my new best friend in the world of data. The functionality was like getting handed a magic wand; it could analyze data without needing me to trip over complex formulas. &lt;/p&gt;

&lt;p&gt;For example, I was once stuck trying to calculate the year-over-year growth of sales figures across various regions, which involved a spreadsheet of about 3,000 rows. Typically, I would have diced and sliced this data manually, hoping to avoid errors along the way. Instead, using Claude, I could simply upload my data set and ask it for the required analysis in plain language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze the sales data and show year-over-year growth by region.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within seconds, not only did Claude give me the results, but it also generated a pivot table for me. I couldn't believe my eyes. The tool had processed the entire sheet seamlessly, saving me at least three hours of work. I was able to use the generated table to make decisions swiftly and present findings during Monday’s meeting with confidence. This was not magic—it was just a smart combination of AI and my ongoing need for efficiency.&lt;/p&gt;

&lt;p&gt;Furthermore, when I felt extra adventurous, I asked Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me the top 5 products based on customer feedback.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, I received a neat summary sans the usual headache. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Principle Behind the Fix
&lt;/h2&gt;

&lt;p&gt;What I learned through this process was simple yet profound: embracing tools that streamline techniques is essential. Rather than getting tangled in the complexity of data manipulation, using Claude for Excel allowed me to focus on what truly mattered—insightful analysis and decision-making. I began to see an increase in my productivity, as I could spend time analyzing results rather than scrambling over formulas. In concrete terms, I've gone from spending about 20 hours a month on data analysis down to fewer than 10.&lt;/p&gt;

&lt;p&gt;So what's the bigger principle here? It's about understanding your limitations and recognizing when to employ technology that complements your skills. Claude for Excel shifted not only the way I handled data but also my perception of it. Instead of feeling overwhelmed, I started feeling empowered, knowing I had a sophisticated partner by my side.&lt;/p&gt;

&lt;p&gt;Reflecting on the time wasted before discovering this tool, I can’t help but chuckle. I wish I had found it sooner. If you're knee-deep in data analysis and feel like you’re drowning in formulas, I highly recommend you take a look at Claude. Just make sure to remember the old saying: it's not about knowing it all, but knowing how to ask the right questions. &lt;/p&gt;

&lt;p&gt;If you've had similar experiences with data tools, what’s the most significant win you've achieved with a new software? What one tool would you recommend to others drowning in data that could provide clarity without the headache?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>github</category>
    </item>
    <item>
      <title>I Struggled with a Bug for Days — Here's What I Learned About Clean Code and Debugging</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Thu, 07 May 2026 15:14:19 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-struggled-with-a-bug-for-days-heres-what-i-learned-about-clean-code-and-debugging-2665</link>
      <guid>https://dev.to/kielltampubolon/i-struggled-with-a-bug-for-days-heres-what-i-learned-about-clean-code-and-debugging-2665</guid>
      <description>&lt;p&gt;As I sat in front of my computer screen one humid evening in Batam, covered with the shadows of unfinished projects, I felt a sinking frustration deep in my chest. For days, I was plagued by a bug in a JavaScript function that seemed to mock me, with every test yielding inconsistent results. I was convinced I had a handle on clean code principles, but the hours spent chasing this elusive problem made me question everything I knew.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context &amp;amp; Stakes
&lt;/h3&gt;

&lt;p&gt;debugging effectively is essential for any developer, especially when working in tight deadlines or collaborative environments. I was leading a small team on a client project, a dynamic web application intended to streamline local business operations. Our success depended on delivering clean, efficient code, and I was the one responsible for reviewing and finalizing it. The stakes were high, and with each failed attempt to fix the issue, our timeline slipped further.&lt;/p&gt;

&lt;p&gt;The bug originated in a function designed to fetch user data from an API, but the data only loaded half the time. After scanning and re-scanning the code, everything seemed fine — I had followed all the recommended practices. Yet, the problem persisted. This relentless loop of testing and frustration forced me to confront my limitations head-on. Would I be able to solve this before our deadline? &lt;/p&gt;

&lt;h3&gt;
  
  
  CHALLENGE
&lt;/h3&gt;

&lt;p&gt;What I had overlooked was a crucial aspect of my code's asynchronous nature. Here’s the snippet that tripped me up:&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;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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;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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&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="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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Network response was not ok&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;data&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;At first glance, this code looked correct, but the challenge was in the way it interacted with the rest of the application. I was using this function in multiple places within the codebase, but because the API could occasionally be slow, I hadn’t handled the failures correctly in the components calling it. This led to intermittent fraying of user experience, with components loading only half the time or throwing unhandled errors.&lt;/p&gt;

&lt;p&gt;I felt the weight of inefficiency weighing down my team. The pressure to deliver without compromising quality left me juggling multiple hats, and I realized that my understanding of clean, maintainable code had been superficial. I needed to dig deeper into both debugging techniques and code readability practices to navigate this complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  BREAKTHROUGH
&lt;/h3&gt;

&lt;p&gt;The breakthrough came when I decided to systematically approach the problem. Instead of diving straight into fixing it, I first resurfaced and rewrote the function to make it clearer and more manageable:&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;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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="kd"&gt;const&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&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="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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Network response was not ok&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetching user data failed: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Logging error with context&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Handle this gracefully on the calling side&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;By adding error handling and a &lt;code&gt;try/catch&lt;/code&gt; block, I octupled my chances of catching errors early. However, the biggest revelation was shifting my mindset from fixing bugs to devising solutions that make the code robust against future issues. I took the time to document the expected outcomes in the comments as a reminder to myself and my teammates. This transparency in the code not only made it easier for us to follow, but it also set the stage for better collaboration.&lt;/p&gt;

&lt;h3&gt;
  
  
  DEEPER INSIGHT
&lt;/h3&gt;

&lt;p&gt;Through this experience, I gleaned a larger principle: clean code is not just about aesthetics or following patterns; it is about making it maintainable and resilient. As developers, we must anticipate how our code may misbehave under unexpected conditions. The cleaner and more explicit we write our code, the easier it becomes for us and others to debug it — saving precious development time in the long run.&lt;/p&gt;

&lt;p&gt;Additionally, the experience reinforced the idea that debugging is as much about mindset as it is about skill. It’s easy to get lost in the weeds of our code; stepping back to reassess the problem can sometimes bring clarity, especially with peer programming, where you can have a fresh set of eyes or someone to help brainstorm.&lt;/p&gt;

&lt;h3&gt;
  
  
  WHAT I'D DO DIFFERENTLY
&lt;/h3&gt;

&lt;p&gt;Upon reflection, here are a few actionable steps I’d take to improve my approach to debugging and code quality moving forward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize Error Handling&lt;/strong&gt;: Always include explicit error handling strategies within asynchronous functions. Errors are inevitable; planning for them pays off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encourage Team Code Reviews&lt;/strong&gt;: Foster a culture of peer reviews within your team. Fresh perspectives often unearth issues that one might overlook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document Potential Errors&lt;/strong&gt;: Add comments to clarify how functions behave in edge cases. Documentation can be a lifesaver.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test-Driven Development (TDD)&lt;/strong&gt;: Implement TDD practices for new features to catch bugs early in the development cycle, enhancing code reliability. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Up Monitoring&lt;/strong&gt;: Utilize tools like Sentry to catch runtime errors and log issues transparently into the application, so you can react in real-time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By taking these steps, I believe we can significantly minimize bugs and build a more resilient infrastructure for our applications. &lt;/p&gt;

&lt;h3&gt;
  
  
  CLOSING QUESTION
&lt;/h3&gt;

&lt;p&gt;What debugging techniques or coding practices have you found essential in your own projects? How have you dealt with seemingly insurmountable bugs? Let's discuss in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>devops</category>
      <category>github</category>
    </item>
    <item>
      <title>I Deployed My First Cloudflare Worker — Here's What I Learned</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Wed, 06 May 2026 11:32:05 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-deployed-my-first-cloudflare-worker-heres-what-i-learned-hcf</link>
      <guid>https://dev.to/kielltampubolon/i-deployed-my-first-cloudflare-worker-heres-what-i-learned-hcf</guid>
      <description>&lt;p&gt;I sat there staring at the screen, my heart racing as I clicked 'deploy' on my first Cloudflare Worker. What should have been a straightforward process felt like a rollercoaster of confusion and excitement. Just moments before, I'd lined everything up: my code looked good, my environment seemed solid—how could anything go wrong?&lt;/p&gt;

&lt;p&gt;But then it hit me. 401 Unauthorized. What? I hadn’t encountered this in all my testing. Little did I know, the culprit was hiding in the shadows of a misconfigured secret token, like a ghost refusing to leave its haunted house.&lt;/p&gt;

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

&lt;p&gt;Being a developer, I often toggle between creative solutions and technical bottlenecks. I’ve spent countless late nights coding, occasionally stacking hundreds of browser tabs filled with information, guides, and articles. Typical developer life, right? In an attempt to re-organize my chaotic digital world, I finally utilized Notion Web Clipper. It became my sidekick, ensuring that everything I stumbled upon would be neatly packed away for when I needed it. But nothing prepared me for the impending chaos with Cloudflare Workers. &lt;/p&gt;

&lt;p&gt;I was eager to build a simple API service that could run globally. After all, deploying on Cloudflare should have been a piece of cake. I picked a couple of templates and dived in, but secret management? Talk about a headache! I had breezed through the initial setup, barely taking note of how tokens should be correctly managed in the environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;The panic kicked in when my worker returned a loud, resounding &lt;strong&gt;401 Unauthorized&lt;/strong&gt; error. I felt defeated. It felt like grasping at straws, trying various approaches that led me further into confusion. I had mistakenly selected a template that wasn’t suitable for my use case, throwing my authorization strategy out the window like an old shoe.&lt;/p&gt;

&lt;p&gt;The main issue was that I lost sight of what tokens I was using for authentication. I had multiple API keys lying around from various services, and sure enough, I grabbed the wrong one. It reminded me of those ‘SELECT A TEMPLATE’ dropdown menus you see on documentation pages that you just don’t read. Instead of reading through the instructions, I assumed I knew it all. Classic developer hubris.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Breakthrough
&lt;/h2&gt;

&lt;p&gt;Eventually, instead of banging my head against the desk, I took a deep breath and revisited the documentation for Cloudflare Workers. Turns out, the secret management was not just a convenient feature; it was crucial for keeping my application secure and functional. They even had detailed instructions on how to handle these secrets correctly.&lt;/p&gt;

&lt;p&gt;I quickly updated my API tokens using Cloudflare's secret management tools with these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Revisit the dashboard:&lt;/strong&gt; Ensure that you've saved your tokens properly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;.env&lt;/code&gt; file:&lt;/strong&gt; If you're unsure of where to place your secrets, Cloudflare Workers let you conveniently manage these in the dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go with the right template:&lt;/strong&gt; Choose wisely! The correct template would seamlessly integrate with your authentication flow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After implementing the right token, I deployed it again. It felt surreal to see the &lt;strong&gt;200 OK&lt;/strong&gt; response this time; everything felt right in the world again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deeper Insight
&lt;/h2&gt;

&lt;p&gt;What I realized through this whole ordeal is much larger than just the technical issue. It served as a reminder of the importance of understanding the tools we use—rushing into a setup without learning the fundamentals can be disastrous. This situation taught me that our tools can either propel our success or hinder it, and we should respect the complexity involved in building software. Reading documentation isn’t just for beginners; it’s for anyone looking to level up.&lt;/p&gt;

&lt;p&gt;In my experience, spending a bit more time upfront in understanding the scope and limits of what I was working on saved me hours of troubleshooting later. &lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Take Notes:&lt;/strong&gt; Throughout the cloud worker setup, I started losing track of what I was doing. If I’d kept notes or a checklist, I could’ve avoided such an embarrassing mistake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t Rush:&lt;/strong&gt; I was so eager to deploy my worker that I skimmed through critical documentation. In the future, I'll be more patient and thorough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging Tools:&lt;/strong&gt; Use Cloudflare's built-in development tools. They can help you pinpoint those pesky authorization issues sooner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask for Help:&lt;/strong&gt; Instead of trying to figure it all out alone, asking in developer communities could save time—like moments of doubt and confusion I faced.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Question
&lt;/h2&gt;

&lt;p&gt;Have you ever overlooked an essential detail in your development work that led to a frustrating error? How did you recover, and what lessons did you take away from it? I’d love to hear your stories in the comments!&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>webdev</category>
      <category>devops</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Thought I Understood State Management in React. Then a Memory Leak Happened.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Wed, 06 May 2026 11:27:58 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-thought-i-understood-state-management-in-react-then-a-memory-leak-happened-14g3</link>
      <guid>https://dev.to/kielltampubolon/i-thought-i-understood-state-management-in-react-then-a-memory-leak-happened-14g3</guid>
      <description>&lt;p&gt;I was deep in debugging a React application one lazy afternoon, staring at the console as error after error cropped up. I had implemented a pretty standard state management pattern to ensure that my component re-renders whenever specific data changed. But somehow, things were behaving strangely. Components were unmounting; states weren’t resetting; everything felt like a mess despite my confidence. What I thought was a straightforward approach was crumbling right in front of my eyes. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Stakes
&lt;/h2&gt;

&lt;p&gt;I was working on an important project that involved managing complex user interactions with real-time data. If I couldn’t contain the bugs swarming around my application, we risked missing a critical deadline. Not just that—I was also concerned about the user experience. A leaked memory could mean sluggish performance, and nobody wants to use an app that feels like it’s dragging its feet. I needed clarity, and I needed it fast. &lt;/p&gt;

&lt;p&gt;Before diving deeper, let's take a look at the state management approach I had employed:&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;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUserData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="nf"&gt;useEffect&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;fetchUserData&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&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;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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/user&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;setUserData&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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;userData&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loading...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seemed fine at first, but things didn’t end up that way. My app started showing out-of-memory errors after several updates, and it felt like a ticking time bomb. I realized the crux of my problem lay in how I managed my component's lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge: Memory Leak Mystery
&lt;/h2&gt;

&lt;p&gt;As I navigated through the sea of warnings in my console, I faced the sudden realization that my fetch operation could stay ongoing even after the component unmounted, leading to a memory leak. If the API call completed after the component was no longer present, I was trying to update an unmounted component's state. This was a classic example of state management gone wrong, resulting in a mess that I had to unravel. &lt;/p&gt;

&lt;p&gt;I had two main problems here: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unsuccessful cleanup of the side effect in &lt;code&gt;useEffect&lt;/code&gt;&lt;/strong&gt;: I hadn’t set up any mechanism to cancel the fetch request once the component unmounted.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaving state updates in a volatile state&lt;/strong&gt;: If &lt;code&gt;fetchUserData&lt;/code&gt; finished running after the component was no longer mounted, it was going to throw an error.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Breakthrough: Fixing the Leak
&lt;/h2&gt;

&lt;p&gt;After reeling back slightly and analyzing the proper way to approach an asynchronous fetch operation within a functional React component, I devised a plan. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use a flag to keep track of the component's status&lt;/strong&gt;: This would help me commune with React's lifecycle effectively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incorporate a cleanup function in &lt;code&gt;useEffect&lt;/code&gt;&lt;/strong&gt;: This would help cancel any ongoing requests or avoid updating the state if the component was no longer active.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s how I revamped my component:&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;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUserData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isMounted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsMounted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="nf"&gt;useEffect&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;setIsMounted&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;abortController&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;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
        &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return &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;setIsMounted&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="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signal&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="kd"&gt;const&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/user&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;signal&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;isMounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;setUserData&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetch request aborted&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error fetching user data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;userData&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loading...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now with the &lt;code&gt;AbortController&lt;/code&gt;, if my &lt;code&gt;fetchUserData&lt;/code&gt; was still going, it would get terminated if the component unmounted before it resolved. Additionally, the &lt;code&gt;isMounted&lt;/code&gt; flag helped me ensure that only my active component’s state updated. I felt relieved to see that my application became snappier, and the error messages evaporated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deeper Insight: Learning from the Experience
&lt;/h2&gt;

&lt;p&gt;As I reflected on how easily I stumbled into a memory leak problem, I understood that state management in React isn't just about keeping track of state; it’s equally about understanding the component lifecycle and properly handling side-effects. Diving deep into the integration of asynchronous functions and how they interact with React’s lifecycle clarified a lot for me. It’s crucial to ensure that everything appears cohesive and doesn't lead to breakage during the component transitions. &lt;/p&gt;

&lt;p&gt;I faced implications from this experience that transcended just fixing bugs. I learned to delve deeper into component lifecycles and how each part of my code interacts. &lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;In hindsight, here are a few actionable steps I would recommend for anyone working with React (or thinking about working with state management): &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always plan for cleanup&lt;/strong&gt;: Understand that with every effect you create, consider what should happen when the component unmounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;AbortController&lt;/code&gt;&lt;/strong&gt;: This is vital for avoiding memory leaks when making fetch requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep track of component status with flags&lt;/strong&gt;: Make sure your state management checks whether the component is mounted before any state updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test regularly&lt;/strong&gt;: Develop a routine for testing the application in various scenarios to catch issues early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How do you manage state and side-effects in your React applications? Have you ever faced memory leak issues before? Let's discuss your solutions and insights!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>devops</category>
      <category>github</category>
    </item>
    <item>
      <title>I Tidied Up Hundreds of Open Tabs with Notion Web Clipper — Here's What I Found</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Tue, 05 May 2026 14:42:29 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-tidied-up-hundreds-of-open-tabs-with-notion-web-clipper-heres-what-i-found-1jop</link>
      <guid>https://dev.to/kielltampubolon/i-tidied-up-hundreds-of-open-tabs-with-notion-web-clipper-heres-what-i-found-1jop</guid>
      <description>&lt;p&gt;It felt overwhelming—hundreds of tabs were open across my browser, each representing a piece of information I once deemed crucial. I had become a digital hoarder, accumulating resources with no plan to revisit them. It was time to act, and that’s when I stumbled upon Notion Web Clipper.&lt;/p&gt;

&lt;h3&gt;
  
  
  A New Approach to Information Management
&lt;/h3&gt;

&lt;p&gt;As a developer based in Batam, Indonesia, my days often blur together as I juggle coding, learning, and keeping up with tech trends. My laptop’s performance suffered with every new tab I opened. I needed a better system to organize my knowledge without the digital clutter weighing me down.&lt;/p&gt;

&lt;p&gt;After hearing about Notion Web Clipper from a fellow developer, I decided to give it a try. It promised an easier way to clip web content and store it neatly within Notion for future reference. Little did I know, this tool would not only streamline my research but also revolutionize the way I approach learning and working.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Tab Overload Challenge
&lt;/h3&gt;

&lt;p&gt;I still remember the day I decided to tackle the mess of open tabs. I had accumulated a collection of tutorials, tool documentation, and countless articles on topics ranging from "Write Code That's Easy to Delete" to AI-driven code reviews. Each tab was a potential goldmine of information but turning into an ever-growing source of distraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here’s the reality:&lt;/strong&gt; My browser was running slowly, and I constantly found myself hunting for specific information amidst the chaos. Frustration was mounting. I realized I had a serious case of tabitus, a term I just coined for my affliction. &lt;/p&gt;

&lt;h3&gt;
  
  
  Finding Relief: The Breakthrough
&lt;/h3&gt;

&lt;p&gt;Using the Notion Web Clipper was a game-changer. I installed the extension and, over the next few days, began clipping articles, guides, and tutorials into designated pages within Notion. I set up a few databases to categorize everything into manageable pieces: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Learning&lt;/strong&gt; - For articles I wanted to study.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; - Documentation for frameworks and libraries I was exploring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspiration&lt;/strong&gt; - Blog posts that sparked an idea for a project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Like magic, I could now search for any topic, relying on either my Notion database or even asking AI for recommendations based on my notes. It transformed my chaos into clarity and significantly improved my productivity. Every time I opened Notion, I felt like I was revisiting a well-organized library instead of a messy cafe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diving Deeper: The Bigger Principle
&lt;/h3&gt;

&lt;p&gt;The real lesson I learned was not just about using Notion Web Clipper. It was about the importance of organization in a developer's workflow. As a programmer, we often emphasize code quality, functionality, and efficiency in our projects, but how often do we apply the same principles to our knowledge management?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Maintaining a clean workspace, both physical and digital, allows for better focus and creativity. When your environment is organized, your mind can function with much more clarity.&lt;/em&gt; I began to see my newfound organization not just as a method but as an essential part of my professional development.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd Do Differently
&lt;/h3&gt;

&lt;p&gt;Reflecting on this journey, here are some actionable steps that I would recommend for anyone looking to tackle their tab overload:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set Time Limits on Tab Openings:&lt;/strong&gt; Create a rule for myself to only open tabs related to current tasks. If something piques my interest, I can clip it instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular Cleanup Sessions:&lt;/strong&gt; Schedule weekly or bi-weekly sessions to revisit tabs and decide what to keep, clip, or close.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engage with Saved Content:&lt;/strong&gt; Allocate specific times to explore clipped resources so they don't just sit idle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Tags and Folders Wisely:&lt;/strong&gt; Tags in Notion make retrieval a breeze. Establish a consistent system for naming and categorizing to avoid future chaos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect with Others:&lt;/strong&gt; Share your insights and systems with fellow developers—feedback can inspire improvements and new approaches to organization.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  A Final Thought
&lt;/h3&gt;

&lt;p&gt;Digital organization might seem trivial to some, but for developers facing a constant flow of information, it can make or break productivity. Have you ever felt overwhelmed by the sheer volume of knowledge accessible today? What systems have you found effective for maintaining clarity in your work? Let’s talk about the methods you’ve discovered in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Surprising Power of Code Reviews: Pull Requests That Saved My Project</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Tue, 05 May 2026 07:59:56 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/the-surprising-power-of-code-reviews-pull-requests-that-saved-my-project-1p89</link>
      <guid>https://dev.to/kielltampubolon/the-surprising-power-of-code-reviews-pull-requests-that-saved-my-project-1p89</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Code reviews. For many developers, they are a necessary evil — a box to check in the development process. However, I have come to appreciate them as a powerful tool for elevating code quality, fostering collaboration, and improving team dynamics. Today, I want to share my journey from viewing code reviews as a mundane task to recognizing their critical role in successful projects.&lt;/p&gt;

&lt;p&gt;Let me take you back to a project I led a few months ago. Our team was tasked with developing a complex web application with collaborative features that would require seamless integration and robust functionalities. In the early days, I saw my role as simply overseeing the process and ensuring that the final product met user expectations. However, I soon learned that underestimating the power of code reviews could lead to colossal mistakes — not just in the code, but in team morale and project outcomes.&lt;/p&gt;

&lt;p&gt;Here’s a detailed account of my experience, the lessons learned, and the actionable strategies that transformed my approach to code reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Early Days: Ignoring Reviews Leading to Mistakes
&lt;/h2&gt;

&lt;p&gt;In the project’s inception phase, I was hyper-focused on deadlines and feature deliveries. Our codebase grew rapidly, but I neglected the importance of formal reviews. I told myself, “I’ll just go over the code later; we need to push this out.” This resulted in my team producing a few hastily written functions with bugs that were not caught early on.&lt;/p&gt;

&lt;p&gt;This atmosphere of speed over quality led to miscommunication within the team. Some developers had differing opinions on the implementation of core features, and since we weren't catching these issues through code reviews, they propelled into the main branch. For instance, an authentication feature, which was crucial, ended up with conflicting logic due to two separate devs implementing their versions without oversight. Here’s a snippet that illustrates this moment:&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="c1"&gt;// Auth function without review&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Assume hashPassword is a utility function we've created&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getUserByUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;hashPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&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;This snippet looks benign, but unreviewed, it ended up breaking several functionalities, leading to a user panic when they faced login issues. A thread of confusion ensued, users were locked out, and our credibility took a hit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson Learned:&lt;/strong&gt; I realized that without code reviews, we were sacrificing not only the integrity of our code but also the trust within our team and with our users. It was a wake-up call that led to the implementation of a stricter code review process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transforming the Code Review Process
&lt;/h2&gt;

&lt;p&gt;Once I recognized the critical importance of code reviews, I began reformulating our approach. The focus shifted from a mere task to a critical collaborative process that involved everyone. Here are the steps we took:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Establish Clear Guidelines
&lt;/h3&gt;

&lt;p&gt;I began by drafting clear, concise guidelines on our code review process. I emphasized that reviews should not only check for bugs but also assess design choices, coding standards, and documentation. Clear expectations set the tone for how we approached reviews. We settled on using GitHub pull requests, which allowed for an easy review process. Here’s an example of our guidelines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose of Review:&lt;/strong&gt; Ensure functionality, maintainability, and clarity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who Reviews:&lt;/strong&gt; At least two team members must review before merging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus Areas:&lt;/strong&gt; Structure, readability, potential edge cases, naming conventions, and performance.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Educating the Team
&lt;/h3&gt;

&lt;p&gt;With our framework in place, it was crucial to educate the team on effective code reviewing. I scheduled a workshop where we discussed best practices. One takeaway was to ask questions rather than impose personal preferences. For example, instead of saying, "Change this variable name to 'userId'," we encouraged feedback like, "What do you think about naming this variable 'userIdentifier' for clarity?"&lt;/p&gt;

&lt;p&gt;This approach not only improved our code quality but also fostered a culture of respect and collaborative growth. I even modeled a review for a particularly tricky component. Here’s a sample of how I structured my feedback:&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;### Feedback on `authenticateUser` function&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Rename the function**&lt;/span&gt;: Consider changing to &lt;span class="sb"&gt;`isAuthenticated`&lt;/span&gt; for clarity on its purpose.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Add Error Handling**&lt;/span&gt;: If the username is not found, currently the function would return undefined, which could lead to confusion. Consider handling this case explicitly.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Utility Usage**&lt;/span&gt;: Instead of comparing raw passwords with hashed values in this method, why not delegate that logic to a utility function? This adds to maintainability.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson Learned:&lt;/strong&gt; A clear structure around code reviews transformed them from a task into an opportunity for everyone on the team to learn from each other’s strengths and weaknesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Culture of Continuous Improvement
&lt;/h2&gt;

&lt;p&gt;With a solid review process in place, I set out to cultivate a culture of continuous improvement. This didn’t happen overnight; it took dedication and encouragement from all team members. We began celebrating great reviews. Whenever a particularly insightful comment improved code quality, we acknowledged it during our team meetings, motivating developers to take pride in their contributions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encourage Pair Programming
&lt;/h3&gt;

&lt;p&gt;I also introduced pair programming sessions, where team members would collaborate on challenging components. This approach not only increased engagement but also minimized the chances of oversight, as developers could hash out design decisions in real-time before code even reached the review phase. Here’s a stripped-down version of an implementation process:&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="c1"&gt;// Pair-programming snippet&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&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="nf"&gt;hashPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashed&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({...&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hashed&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="c1"&gt;// Account for registration without review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet encapsulates ideas generated during pair programming sessions where we were able to identify potential flaws before they made their way to the review process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson Learned:&lt;/strong&gt; Investing in relationships through pair programming not only improved the quality of the code but also strengthened our team’s commitment to collaborative improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;After implementing a robust code review process, I witnessed a significant transformation not just in our code quality, but also in team morale. Challenges that previously had the power to derail us became collaborative learning opportunities. We moved from a culture of blame and speed to one of accountability and quality.&lt;/p&gt;

&lt;p&gt;Now I see code reviews as a vital part of the software development lifecycle, a necessity for building better software and a stronger team. I’d love to hear from you: What has been your experience with code reviews? Have you faced challenges in your process or found unexpected benefits? Let's discuss in the comments below!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>github</category>
    </item>
    <item>
      <title>I Shipped My First Cloudflare Worker via GitHub Actions in 47 Minutes (3 Were Wasted on the Wrong API Token)</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Tue, 05 May 2026 03:36:33 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-shipped-my-first-cloudflare-worker-via-github-actions-in-47-minutes-3-were-wasted-on-the-wrong-1l4n</link>
      <guid>https://dev.to/kielltampubolon/i-shipped-my-first-cloudflare-worker-via-github-actions-in-47-minutes-3-were-wasted-on-the-wrong-1l4n</guid>
      <description>&lt;p&gt;My first Cloudflare Worker deployed in 47 minutes. Three of those were spent staring at this exact error in a red GitHub Actions log: &lt;code&gt;Authentication error [code: 10000]&lt;/code&gt;. I had the API token. I had the account ID. I had copy-pasted the workflow from the official docs. It still failed.&lt;/p&gt;

&lt;p&gt;The fix was one checkbox I never selected. That checkbox is the entire reason I'm writing this post, because every tutorial I read assumed I would not get it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A Cloudflare Worker that returns a JSON response saying hello. Three lines of actual logic. One &lt;code&gt;wrangler.toml&lt;/code&gt; file. One GitHub Actions workflow. Push to &lt;code&gt;main&lt;/code&gt;, the Worker is live on the edge, end of story.&lt;/p&gt;

&lt;p&gt;The point was never the Worker itself. The point was getting the pipeline working so the next 100 commits ship themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup That Actually Works
&lt;/h2&gt;

&lt;p&gt;Here is the worker. It lives at &lt;code&gt;src/index.js&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from the edge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;colo&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&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="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;wrangler.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hello-edge"&lt;/span&gt;
&lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/index.js"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2025-01-01"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the GitHub Actions workflow at &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Worker&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudflare/wrangler-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;apiToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CLOUDFLARE_API_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CLOUDFLARE_ACCOUNT_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole thing. Three files. Roughly 25 lines including blanks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 Minutes I Want Back
&lt;/h2&gt;

&lt;p&gt;The first time I pushed, the Action failed in 11 seconds with &lt;code&gt;Authentication error [code: 10000]&lt;/code&gt;. I assumed I had pasted the token wrong. I rotated it. Same error. I checked the secret name. Correct. I read the wrangler-action README twice. I started questioning the structure of reality.&lt;/p&gt;

&lt;p&gt;What I had done: created a Cloudflare API token using the &lt;strong&gt;"Read All Resources"&lt;/strong&gt; template because I was being cautious. That token can read everything and write nothing. Wrangler needs to write. The fix was to use the &lt;strong&gt;"Edit Cloudflare Workers"&lt;/strong&gt; template instead, which scopes write access to exactly the Workers resource and nothing else.&lt;/p&gt;

&lt;p&gt;The reason this isn't obvious from the error: code 10000 is Cloudflare's generic auth failure. It does not say "your token has no write permission." It says "auth bad." Three minutes of my life, gone, to a missing checkbox.&lt;/p&gt;

&lt;p&gt;The other gotcha most posts skip: the &lt;strong&gt;Account ID is not a secret&lt;/strong&gt;. It's visible in your dashboard URL. Storing it as a GitHub secret is fine, but it's not protecting anything sensitive. The API token is the actual key to the kingdom. Do not commit it. Do not log it. Rotate it if you so much as look at it sideways in a public terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Bothered With CI/CD on Day One
&lt;/h2&gt;

&lt;p&gt;Honest answer: I almost didn't. The Cloudflare dashboard has a perfectly fine "Connect to Git" button that wires up auto-deploys without writing a single line of YAML. For a beginner shipping one Worker, that is the faster path.&lt;/p&gt;

&lt;p&gt;I went with GitHub Actions anyway because of one thing: control. The wrangler-action approach lets me add steps I'll need later, like running tests before deploy, deploying to a staging environment first, posting to Slack on failure, gating on a manual approval. The dashboard integration gives me a black box. The Actions workflow gives me a file I can read.&lt;/p&gt;

&lt;p&gt;The trade is more setup time now (about 15 extra minutes) for unlimited flexibility later. For a learning project, that's the right trade. For shipping a one-off marketing site, just click the button.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Successful Deploy
&lt;/h2&gt;

&lt;p&gt;It logged this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Total Upload: 0.42 KiB / gzip: 0.30 KiB
Uploaded hello-edge (1.15 sec)
Deployed hello-edge triggers (0.32 sec)
&lt;/span&gt;&lt;span class="gp"&gt;  https://hello-edge.&amp;lt;my-subdomain&amp;gt;&lt;/span&gt;.workers.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I clicked the URL. It loaded in 38 milliseconds from a Singapore data center. I am writing this from Batam, Indonesia, so that's roughly 60 km away. My code, on a server, 60 km from me, deployed by a GitHub Action I wrote 47 minutes ago.&lt;/p&gt;

&lt;p&gt;The thing I was not expecting: the satisfaction came from the pipeline, not the Worker. Pushing to &lt;code&gt;main&lt;/code&gt; and watching the green check appear on GitHub, knowing my Worker is now live globally without me touching the Cloudflare dashboard, that's the actual feature. The Worker is a hello world. The CI/CD is the real thing I built.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Teaches
&lt;/h2&gt;

&lt;p&gt;Three things, if you're about to do this for the first time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the "Edit Cloudflare Workers" API token template, not "Read All Resources" or any custom one you build yourself.&lt;/strong&gt; The pre-baked template has the exact scopes wrangler needs. Custom tokens are how you lose 3 minutes to error code 10000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set up the Actions pipeline before your code is interesting.&lt;/strong&gt; A boring Worker behind a working pipeline is worth more than a clever Worker you deploy by hand. The pipeline compounds. Your hand-deploys do not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Account ID is public, the API token is not.&lt;/strong&gt; Store both as secrets if it makes your life easier, but understand which one matters. One leak is a footgun. The other leak is a key handed to a stranger.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Building Next
&lt;/h2&gt;

&lt;p&gt;Next step is preview deploys per pull request, so every PR gets its own &lt;code&gt;*.workers.dev&lt;/code&gt; URL automatically. I think the answer is a second job in the workflow keyed on &lt;code&gt;pull_request&lt;/code&gt;, plus a comment bot that posts the preview URL on the PR.&lt;/p&gt;

&lt;p&gt;If you've done this with Workers (not Pages, that's the easy mode), what does your workflow look like? And what's the one CI/CD mistake you wish someone had warned you about on day one?&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>github</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Connected 11 MCP Servers. 3 of Them Actually Did Anything.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Mon, 04 May 2026 14:39:34 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-connected-11-mcp-servers-3-of-them-actually-did-anything-4gcf</link>
      <guid>https://dev.to/kielltampubolon/i-connected-11-mcp-servers-3-of-them-actually-did-anything-4gcf</guid>
      <description>&lt;p&gt;I spent a weekend connecting every MCP server that sounded useful. By Sunday night I had 11 running, a &lt;code&gt;claude_desktop_config.json&lt;/code&gt; that scrolled off the screen, and an agent that was technically capable of doing almost anything. In practice, it was doing almost nothing useful.&lt;/p&gt;

&lt;p&gt;What I learned had very little to do with which servers are "good."&lt;/p&gt;

&lt;h2&gt;
  
  
  The List Looks Better Than It Performs
&lt;/h2&gt;

&lt;p&gt;The MCP ecosystem has exploded. There are directories with thousands of servers now. You can connect your agent to GitHub, Notion, Google Drive, Slack, your calendar, your email, your databases, your browser, a web search tool, a weather API, and about 40 other things I cannot remember anymore. The pitch is always the same: connect everything and your agent becomes superhuman.&lt;/p&gt;

&lt;p&gt;What nobody tells you is what happens to your context window when you do.&lt;/p&gt;

&lt;p&gt;Every MCP server shows up in your agent's prompt as a block of tool definitions: name, description, parameter schema. This is not free. Perplexity's CTO said publicly in March that tool descriptions alone eat 40 to 50% of available context before the agent touches a single real task. I ran my own rough test with 11 servers and came out around 35%. One third of my agent's thinking capacity, gone before the first message. The agent still technically worked. It just worked slower, cost more per turn, and occasionally picked the wrong tool because it had 80 options to reason about instead of a handful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Kept
&lt;/h2&gt;

&lt;p&gt;After a week of watching which tools the agent actually called, I stripped it down to three: web search, my calendar, and file system access to one specific folder.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;claude_desktop_config.json&lt;/code&gt; went from this kind of sprawl:&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;"brave-search"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-brave-search"&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;"github"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-github"&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;"notion"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-notion"&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;"slack"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-slack"&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;"google-calendar"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-google-calendar"&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;"filesystem"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-filesystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/you"&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;"puppeteer"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-puppeteer"&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;To this:&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;"brave-search"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-brave-search"&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;"google-calendar"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-google-calendar"&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;"filesystem"&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;"npx"&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;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-filesystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/you/work"&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;Note the filesystem path change. &lt;code&gt;/Users/you&lt;/code&gt; gave the agent access to everything on my machine. &lt;code&gt;/Users/you/work&lt;/code&gt; gives it access to what it actually needs. Smaller scope, less noise in file lookups, faster responses.&lt;/p&gt;

&lt;p&gt;The difference was immediate and embarrassing. I had spent a whole weekend adding capabilities, and the agent got noticeably better the moment I removed most of them.&lt;/p&gt;

&lt;p&gt;Web search is the tool that punches farthest above its weight. An agent that can look something up in real time stops being a static oracle and becomes useful for questions whose answers change. Calendar access mattered more than I expected, not because the agent scheduled things for me, but because it could check whether I had time before suggesting I do something. "You have a 2-hour block on Thursday" as context is qualitatively different from a suggestion with no time anchor. File system access scoped to one working folder let me point the agent at my notes and drafts without handing it my entire machine.&lt;/p&gt;

&lt;p&gt;Everything else I connected was called once or twice across a full week of actual use, then never again. GitHub: technically impressive, practically unnecessary because I just used the CLI. Notion: ran three queries, never opened it through the agent again. Slack: the agent drafted messages I never sent. Browser automation: impressive in a demo, zero practical value in my real workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rule I Wish Someone Had Told Me Earlier
&lt;/h2&gt;

&lt;p&gt;A connected MCP server costs you context whether or not you ever use it. The tool definitions sit in the prompt on every single turn. The agent reasons about whether to invoke them each time. More servers is not "free and neutral, then occasionally useful." More servers is always a cost, and only sometimes worth it. That is a fundamentally different calculation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An MCP list that looks impressive in a screenshot will slow your agent down in production. Connect what you actually use, not what sounds good.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before adding any server, the right question is not "could this be useful?" It is "did I actually need this in the last seven days?" If the answer is no, leave it disconnected. You can always add it back in thirty seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;Start with zero servers. Use the agent for a week on its base model alone. Write down every moment you hit a wall, something the agent could not do that you genuinely needed. Then connect exactly those servers and nothing else. Nothing else.&lt;/p&gt;

&lt;p&gt;Most of the value in AI agents right now does not come from the size of the tool list. It comes from the agent having enough context headroom to reason clearly about the few tools it has. A focused agent with 3 servers beats a loaded one with 11. At least that is what a weekend of overengineering taught me.&lt;/p&gt;




&lt;p&gt;If you have used MCP in a real workflow beyond demos, I want to know two things: which server do you use every single day, and which one did you connect and quietly never call again? Drop both. The most useful answers get turned into a proper teardown post with real usage data.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Built an OSINT Aggregator That Queries 5 Threat Intel Sources in One Command</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Fri, 01 May 2026 11:54:36 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-built-an-osint-aggregator-that-queries-5-threat-intel-sources-in-one-command-4g5p</link>
      <guid>https://dev.to/kielltampubolon/i-built-an-osint-aggregator-that-queries-5-threat-intel-sources-in-one-command-4g5p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;One night. One command. Full threat intelligence picture.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Every time I needed to check if an IP or domain was malicious, I'd end up with &lt;strong&gt;5 browser tabs open&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VirusTotal&lt;/li&gt;
&lt;li&gt;AlienVault OTX&lt;/li&gt;
&lt;li&gt;Shodan&lt;/li&gt;
&lt;li&gt;NVD CVE Database&lt;/li&gt;
&lt;li&gt;GitHub Security Advisories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copy. Paste. Wait. Repeat. Cross-reference. Then make a decision.&lt;/p&gt;

&lt;p&gt;This workflow is fine once. But when you're triaging multiple alerts during an incident, or doing OSINT research on a list of indicators — it's a massive time sink.&lt;/p&gt;

&lt;p&gt;So I built a tool to automate it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing SentinelScout
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SentinelScout&lt;/strong&gt; is an open-source CLI tool that queries 5 threat intelligence sources simultaneously, then uses AI to correlate the results into a single, actionable threat score.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sentinelscout query suspicious-domain.xyz
&lt;span class="go"&gt;
Query: suspicious-domain.xyz (5 sources)

Source         Status  Severity  Result
──────────────────────────────────────────────
Virustotal      [+]     HIGH     67/94 engines flagged
Alienvault      [+]     HIGH     3 pulses found, tags: [apt] [phishing]
Shodan          [+]     INFO     4 open ports detected
Cve             [-]     LOW      No known CVEs found
Github Adv      [-]     LOW      No advisories found

Threat Score: 87/100 [CRITICAL]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No browser tabs. No copy-pasting. Just answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The tool is built on three pillars:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Async Python — Everything Runs in Parallel
&lt;/h3&gt;

&lt;p&gt;All 5 sources are queried concurrently using Python's &lt;code&gt;asyncio&lt;/code&gt;. Results come back in 2-4 seconds total, regardless of how slow any single source is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sources_filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AnalysisReport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;all_sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BaseSource&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;VirusTotalSource&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;AlienVaultSource&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;ShodanSource&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;CVESource&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;GitHubAdvSource&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="n"&gt;sources_filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;all_sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_sources&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sources_filter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_sources&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AnalysisReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indicator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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;asyncio.gather()&lt;/code&gt; call fires all 5 API requests simultaneously. If one source is slow, it doesn't block the others. Total wait time = slowest source, not sum of all sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Source Adapters — Pluggable Architecture
&lt;/h3&gt;

&lt;p&gt;Each threat intel source has its own adapter class that handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API authentication&lt;/li&gt;
&lt;li&gt;Request/response handling&lt;/li&gt;
&lt;li&gt;Data normalization to a standard &lt;code&gt;IOCResult&lt;/code&gt; model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a new source is just creating a new class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MySource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseSource&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mysource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MYSOURCE&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;IOCResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# your scraping logic here
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;IOCResult&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every adapter returns the same shape. The CLI doesn't care which source it came from — it just formats and displays it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI Correlation — Making Sense of It All
&lt;/h3&gt;

&lt;p&gt;When you add an OpenAI API key, GPT-4o-mini reads all the raw results and generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A plain-English threat summary&lt;/li&gt;
&lt;li&gt;A 0-100 threat score&lt;/li&gt;
&lt;li&gt;A severity label (LOW / MEDIUM / HIGH / CRITICAL)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful when different sources give conflicting signals — AI helps you make a judgment call instead of staring at contradictory data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Supported Sources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;API Required&lt;/th&gt;
&lt;th&gt;Free Tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VirusTotal&lt;/td&gt;
&lt;td&gt;Domain/IP/Hash&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;500 req/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AlienVault OTX&lt;/td&gt;
&lt;td&gt;IP/Domain/Hash&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;10k pulses/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shodan&lt;/td&gt;
&lt;td&gt;IP only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100 queries/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVD CVE&lt;/td&gt;
&lt;td&gt;Vulnerabilities&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Advisories&lt;/td&gt;
&lt;td&gt;Vulnerabilities&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two sources (CVE and GitHub) work out of the box with no API key needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;sentinelscout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Get free API keys:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VirusTotal:&lt;/strong&gt; &lt;a href="https://www.virustotal.com/gui/my-apikey" rel="noopener noreferrer"&gt;virustotal.com/gui/my-apikey&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AlienVault OTX:&lt;/strong&gt; &lt;a href="https://otx.alienvault.com/api" rel="noopener noreferrer"&gt;otx.alienvault.com/api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shodan:&lt;/strong&gt; &lt;a href="https://account.shodan.io" rel="noopener noreferrer"&gt;account.shodan.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI:&lt;/strong&gt; &lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;platform.openai.com/api-keys&lt;/a&gt; &lt;em&gt;(optional)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Set your keys:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VIRUSTOTAL_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ALIENVAULT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SHODAN_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key  &lt;span class="c"&gt;# optional&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sentinelscout query suspicious-domain.xyz
sentinelscout sources   &lt;span class="c"&gt;# check config status&lt;/span&gt;
sentinelscout &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sentinelscout/
├── cli.py              # CLI entrypoint (Typer + Rich)
├── analyzer.py         # AI correlation engine (OpenAI)
├── config.py           # .env loader
├── models.py           # IOCResult, AnalysisReport dataclasses
├── sources/
│   ├── base.py         # BaseSource abstract class
│   ├── virustotal.py   # VirusTotal API client
│   ├── alienvault.py   # AlienVault OTX client
│   ├── shodan.py       # Shodan API client
│   ├── cve.py          # NVD CVE feed scraper (no key needed)
│   └── github.py       # GitHub Security Advisories (no key needed)
└── pyproject.toml      # package metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;&lt;strong&gt;Async Python is powerful.&lt;/strong&gt; Once you wrap your head around &lt;code&gt;asyncio.gather()&lt;/code&gt;, you start seeing parallelism everywhere. It's the difference between a tool that takes 15 seconds and one that takes 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured data models &amp;gt; raw dicts.&lt;/strong&gt; Defining &lt;code&gt;IOCResult&lt;/code&gt; and &lt;code&gt;AnalysisReport&lt;/code&gt; as clean dataclasses made the entire codebase simpler. Every source adapter returns the same shape. No more ad-hoc dict handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best projects come from personal pain.&lt;/strong&gt; I didn't build SentinelScout to impress anyone. I built it because I was annoyed with my own workflow. That specific irritation is what kept me going through the 3-hour build session — and it's what makes the tool actually useful, not just impressive-looking.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Ideas on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Web UI&lt;/strong&gt; (Gradio or Streamlit) for non-CLI users&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;JSON/CSV export&lt;/strong&gt; for SIEM integration&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;Telegram bot&lt;/strong&gt; for on-the-go IOC queries&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;More sources:&lt;/strong&gt; GreyNoise, AbuseIPDB, ThreatFox&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Get the Code
&lt;/h2&gt;

&lt;p&gt;Full source code, installation instructions, and documentation:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/glatinone/sentinelscout" rel="noopener noreferrer"&gt;github.com/glatinone/sentinelscout&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Star it if you find it useful. Issues and PRs welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Python 3.10+, Typer, Rich, and httpx. No paid dependencies — runs entirely on free-tier APIs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>cybersecurity</category>
      <category>opensource</category>
      <category>osint</category>
    </item>
    <item>
      <title>I Let Claude Pentest My Own Side Project for $0.43. It Found Three Things in 12 Minutes.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Mon, 27 Apr 2026 16:38:31 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-let-claude-pentest-my-own-side-project-for-043-it-found-three-things-in-12-minutes-lab</link>
      <guid>https://dev.to/kielltampubolon/i-let-claude-pentest-my-own-side-project-for-043-it-found-three-things-in-12-minutes-lab</guid>
      <description>&lt;p&gt;I built ContextForge to solve my own problem: AI chats that lose memory mid-project. Last night I gave Claude the codebase and asked it to find ways to break in. Twelve minutes and forty-three cents later, I had a finding sheet I would have been embarrassed to show a senior engineer.&lt;/p&gt;

&lt;p&gt;This post is what I did, what it found, what it missed, and how to do the same to your own side project tonight without spending more than a dollar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;ContextForge is a small open-source app for managing AI conversation context. Next.js on the front, FastAPI on the back, SQLite for storage. Roughly 4,200 lines of TypeScript and Python at the time of the audit. Personal project, not yet shipped, but already wired up to a real LLM API and already storing real conversation data on disk.&lt;/p&gt;

&lt;p&gt;In other words, exactly the class of side project most developers never security-review. We treat side projects like sketches and production apps like buildings. The problem is that side projects ship to production constantly, often with the same database engine and the same secret-handling habits we'd use in a real product.&lt;/p&gt;

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

&lt;p&gt;I wanted a junior pentester for one evening. Not a vendor scanner, not a SaaS audit tool. Just a smart adversary with patience. So I gave Claude three things:&lt;/p&gt;

&lt;p&gt;A clear adversarial role. "You are a senior application security engineer. Your job is to find ways an attacker could compromise this app, exfiltrate data, or escalate privileges. Be specific. Cite line numbers. Rank findings by severity."&lt;/p&gt;

&lt;p&gt;The repo. I dumped the relevant files (FastAPI routes, the Next.js API handlers, the SQLite schema, the .env.example, the Docker setup) into the conversation.&lt;/p&gt;

&lt;p&gt;Scope rails. "Focus on the API surface, secret handling, input validation, and storage. Skip dependency CVEs for this pass. Skip nice-to-haves. Only flag things that would matter if this app went public tomorrow."&lt;/p&gt;

&lt;p&gt;The whole prompt was about 200 words. The repo dump was about 18,000 tokens. Total session cost on the Anthropic API: $0.43 across two back-and-forth rounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Found
&lt;/h2&gt;

&lt;p&gt;Three findings. All real. All embarrassing in different ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 1: API key leaking through error responses
&lt;/h3&gt;

&lt;p&gt;My FastAPI exception handler returned the full exception message to the client on 500 errors. One of those exceptions, raised when the LLM client failed to authenticate, included the partial API key in its message. A debug aid I forgot was there. An attacker who could trigger that specific failure path would get back a fragment of my OpenRouter key in the JSON response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# api/routes/context.py (the bug)
&lt;/span&gt;&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/extract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ExtractRequest&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;llm_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# bug: this leaks internal error text including upstream API errors
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is one line of paranoia. Catch the exception, log the real error server-side, return a generic message to the client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# api/routes/context.py (the fix)
&lt;/span&gt;&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/extract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ExtractRequest&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;llm_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extract_context failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extraction failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had written this exact pattern correctly in three other routes. I missed it on this one because I was tired when I added it. This is the most common shape of a real vulnerability: not a clever attack, just a tired Tuesday.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 2: SQLite path traversal in the per-project save feature
&lt;/h3&gt;

&lt;p&gt;ContextForge lets you save context packs per project. I was using the project name as part of the file path. No sanitization. A project named &lt;code&gt;../../../etc/passwd&lt;/code&gt; would happily walk out of the data directory.&lt;/p&gt;

&lt;p&gt;Claude flagged the exact line, suggested a deny-list (which I rejected because deny-lists always lose), and on follow-up suggested a UUID-based storage scheme where the user-facing name is decoupled from the on-disk path. That is the right answer. I was using the name because it was convenient during dev. Convenient during dev is the most dangerous phrase in security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding 3: CORS set to wildcard in the FastAPI config
&lt;/h3&gt;

&lt;p&gt;I had &lt;code&gt;allow_origins=["*"]&lt;/code&gt; for "local development convenience" in a config file that was also being loaded in the Docker production target. If I had pushed this to a public deployment as-is, any website on the internet could have made authenticated requests to the API from a victim's browser. This is the kind of thing that gets caught by a real code review and almost never gets caught by a unit test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Missed
&lt;/h2&gt;

&lt;p&gt;This is the part most "I used AI for security" posts skip, and it's the part that matters.&lt;/p&gt;

&lt;p&gt;Claude did not catch a race condition in the project deletion endpoint, where deleting a project while a context extraction was in flight would leave orphan rows in the embeddings table. I found that one myself two days later when something broke in the UI. It's the kind of bug that requires understanding the full data flow over time, not just reading individual files.&lt;/p&gt;

&lt;p&gt;It also did not catch the fact that my rate limiter was applied per IP and my dev environment was running everything through localhost, meaning the rate limiter was effectively disabled for all real testing. A pentester who uses the app for a day would have noticed this. A model reading the code in isolation didn't.&lt;/p&gt;

&lt;p&gt;The lesson isn't that AI security review is bad. The lesson is that it's a good first pass. It catches the ten things I should have caught myself before I shipped. It does not replace someone who actually uses the system over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost Math That Surprised Me
&lt;/h2&gt;

&lt;p&gt;Total tokens for the session: roughly 47,000 input, 8,200 output. Total cost on the Anthropic API: $0.43. Twelve minutes of wall-clock time. For comparison, the cheapest external code review I have ever paid for cost $300 and took a week.&lt;/p&gt;

&lt;p&gt;That is not a fair comparison, of course. A human reviewer brings context and judgment Claude does not have. But for the specific job of "find the obvious things in my own code before I ship," forty-three cents is the right ballpark and twelve minutes is the right wall-clock.&lt;/p&gt;

&lt;p&gt;I would happily pay this every time I'm about to push a side project to a public URL. I am, in fact, going to make it a checklist item.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Actually Teaches You
&lt;/h2&gt;

&lt;p&gt;A frontier model is a free junior pentester on your own code, with two caveats. First, you must give it adversarial scope, not "review my code." The framing matters because models default to politeness, and a polite reviewer does not find leaks in error responses. Second, you must verify the findings yourself. The cost of acting on a false-positive is wasted time. The cost of acting on a real finding is one fewer embarrassment.&lt;/p&gt;

&lt;p&gt;The post-mortem habit matters more than the tool. Every finding above was a thing past-me knew was wrong and present-me had stopped paying attention to. The audit was not new knowledge. It was a forced re-read with a hostile lens.&lt;/p&gt;

&lt;p&gt;If you are sitting on a side project that has crept toward something real (a small SaaS, a portfolio piece with a live URL, a tool you started selling on the side), run this audit tonight. Forty-three cents and twelve minutes is the right price for sleeping better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Five-Step Checklist If You Want To Run This Tonight
&lt;/h2&gt;

&lt;p&gt;Pick the project. Anything with a public URL or a real database counts.&lt;/p&gt;

&lt;p&gt;Dump the security-relevant files into a fresh chat. API routes, config, env example, schema, auth middleware.&lt;/p&gt;

&lt;p&gt;Use the adversarial prompt: senior application security engineer, find specific vulnerabilities with line numbers, ranked by severity, scoped to API surface and secrets and storage.&lt;/p&gt;

&lt;p&gt;Read every finding. Verify against your code. Reject the false positives.&lt;/p&gt;

&lt;p&gt;Fix the real ones before you sleep. Otherwise tomorrow you will not.&lt;/p&gt;




&lt;p&gt;What is the smallest, most embarrassing thing an AI security review found in your own code? And what is the one finding you are still arguing with the model about because you don't want to fix it? Drop both in the comments. The most surprising answer becomes the next post, with credit.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Let OpenClaw Run My Mornings for 7 Days. It Stopped Acting Like a Chatbot on Day 4.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Sat, 25 Apr 2026 14:30:14 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-let-openclaw-run-my-mornings-for-7-days-it-stopped-acting-like-a-chatbot-on-day-4-40j5</link>
      <guid>https://dev.to/kielltampubolon/i-let-openclaw-run-my-mornings-for-7-days-it-stopped-acting-like-a-chatbot-on-day-4-40j5</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the OpenClaw Writing Challenge&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For three days I almost uninstalled OpenClaw. I had connected it to Telegram, GitHub, and Google Calendar, and every morning at 8 AM it sent me the most useless message I have ever received from a piece of software: "You have 4 open PRs. Consider reviewing them."&lt;/p&gt;

&lt;p&gt;Thanks. I have eyes. The breakthrough came on day 4, and it had nothing to do with the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A morning triage agent. Not a chatbot waiting for me to ask. An agent that wakes up at 8 AM, observes the state of my work across three tools, and tells me the one thing I should do first that day, with a reason.&lt;/p&gt;

&lt;p&gt;The chatbot mindset stops at "summarize my morning." The agent mindset closes the loop and makes a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The skill definition is the contract. Plain English, declarative, and OpenClaw figures out the rest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# skills/morning-triage/SKILL.md&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;morning-triage&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;Every morning at 8 AM, look at my open GitHub PRs,&lt;/span&gt;
  &lt;span class="s"&gt;unread Telegram messages, and today's calendar.&lt;/span&gt;
  &lt;span class="s"&gt;Decide the ONE thing I should do first and tell me why.&lt;/span&gt;
&lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;github&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;telegram&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;calendar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner is the observe → decide → act loop. This is what every real agent looks like underneath.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# skills/morning-triage/run.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openclaw&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TriageAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open_prs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kiel/myrepo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;msgs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;now&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Given this state: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the SINGLE most important thing I should do &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in the next 90 minutes? Name the specific PR/message/event. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;One sentence. Include WHY it matters.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;☕ Focus today: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nc"&gt;TriageAgent&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chatbot mindset stops at "decide" because the human is supposed to act on the answer. The agent closes the loop on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Happened
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 1.&lt;/strong&gt; Output: "You have 4 open PRs. Consider reviewing them." Useless. I almost killed the cron.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 2.&lt;/strong&gt; Same. I had given the agent enough tools to see everything but not enough instruction to choose. The model was hedging. The fault was in my prompt, not the LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 3.&lt;/strong&gt; I rewrote the decide step. Forced it to name a specific item. Forced it to explain why. Forced it into one sentence. Output got slightly better but still felt mechanical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 4.&lt;/strong&gt; The breakthrough. I added one extra field to observe: how long each PR had been open and how many times someone had pinged me. Suddenly the agent had real signal. The morning message read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☕ Focus today: PR #142 has been open 9 days, blocking the v2.3 release, and Andre has pinged you twice on Telegram about it. Review it before your 10:30 standup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had genuinely forgotten about that PR. The agent connected three separate data sources and surfaced a thread I had dropped two weeks ago. That was the moment OpenClaw stopped being a chatbot to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 5.&lt;/strong&gt; Different output, different decision: "Skip the deep work block, you have a 10:00 meeting that conflicts with your 10:15 review and three unread messages from your manager since last night."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 6.&lt;/strong&gt; It correctly told me to do nothing urgent and protect my deep work block. That was the second moment that surprised me. A good agent has to know when not to interrupt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 7.&lt;/strong&gt; Total cost for the week was under one dollar in API spend, because the agent only fires once a day on a schedule. Chatbot usage is unbounded. Scheduled agent usage is cheap and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson Behind the Lesson
&lt;/h2&gt;

&lt;p&gt;The fix was never the model. The fix was three things, in this order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better observation.&lt;/strong&gt; If you do not give the agent state, it cannot make a decision. Vague input produces vague output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forced specificity in the prompt.&lt;/strong&gt; "Name the specific item. Explain why. One sentence." That is the difference between a horoscope and a recommendation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A schedule, not a conversation.&lt;/strong&gt; The chatbot pattern bills you per question. The agent pattern bills you per outcome.&lt;/p&gt;

&lt;p&gt;If you are typing into OpenClaw the same way you type into ChatGPT, you are using a power tool to hammer in a nail. Write one SKILL.md. Set one schedule. Close the loop. That is the unlock.&lt;/p&gt;




&lt;p&gt;If you had to give one OpenClaw agent full control over a part of your day, what would you let it decide for you, and what would you never let it touch? I am genuinely curious where the line is for other devs. Drop it below.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>What's New in GKE at Next '26: Kubernetes Just Got Smarter (and Cheaper)</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Thu, 23 Apr 2026 14:01:43 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/whats-new-in-gke-at-next-26-kubernetes-just-got-smarter-and-cheaper-1md</link>
      <guid>https://dev.to/kielltampubolon/whats-new-in-gke-at-next-26-kubernetes-just-got-smarter-and-cheaper-1md</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-cloud-next-2026-04-22"&gt;Google Cloud NEXT Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes as the OS of the AI Era
&lt;/h2&gt;

&lt;p&gt;At Google Cloud Next '26, Google made one thing pretty clear: &lt;strong&gt;Kubernetes is no longer just a container orchestrator. It's quickly becoming the operating system of the AI era.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The numbers speak for themselves. GKE now powers AI workloads for all of Google's top 50 customers on the platform, including the biggest frontier model builders out there. In just a few months, multi-agent AI workflows surged by &lt;strong&gt;327%&lt;/strong&gt;. On top of that, 66% of organizations now rely on Kubernetes to run their generative AI apps and agents.&lt;/p&gt;

&lt;p&gt;This isn't just an incremental update. It's a real shift. And the GKE announcements at Next '26 reflect that change well. Here's a breakdown of what's new and why it actually matters for developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. GKE Agent Sandbox: Secure, Scalable Agent Infrastructure
&lt;/h2&gt;

&lt;p&gt;As AI moves from chatbots to fully autonomous agents running at massive scale, the underlying infrastructure needs to keep up with hundreds, sometimes thousands, of agents collaborating at the same time.&lt;/p&gt;

&lt;p&gt;Google's answer to this is the &lt;strong&gt;GKE Agent Sandbox&lt;/strong&gt;, which they're calling the industry's most scalable and low-latency agent infrastructure. It's built on &lt;strong&gt;gVisor kernel isolation&lt;/strong&gt; (the same tech that secures Gemini), so you can safely run untrusted code, tools, and entire agents without giving up performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;300 sandboxes per second at sub-second latency&lt;/li&gt;
&lt;li&gt;Up to 30% better price-performance on Axion compared to other hyperscale clouds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One real-world example: &lt;strong&gt;Lovable&lt;/strong&gt;, a platform where builders spin up 200,000+ new projects every single day, runs its AI-generated apps in GKE Agent Sandboxes. The reason? Fast startup, fast scaling, and solid secure isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means for you:&lt;/strong&gt; If you're building multi-agent systems or executing untrusted user-generated code, Agent Sandbox takes away the painful choice between security and speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. GKE Hypercluster: One Control Plane for Everything
&lt;/h2&gt;

&lt;p&gt;Let's be honest, managing hundreds of disconnected Kubernetes clusters is a nightmare at scale.&lt;/p&gt;

&lt;p&gt;GKE Hypercluster (now in private GA) tackles this head on by letting &lt;strong&gt;a single, Kubernetes-conformant GKE control plane manage 1 million chips across 256,000 nodes across multiple Google Cloud regions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What makes it stand out is the security model. It uses Google's &lt;strong&gt;Titanium Intelligence Enclave&lt;/strong&gt;, a software-hardened "no-admin-access" security engine. Your proprietary model weights and prompts stay cryptographically sealed from platform admins and infrastructure layers. That's a big deal for enterprise teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means for you:&lt;/strong&gt; If you're running large-scale AI training across regions, you no longer have to deal with fragmented clusters. Your entire distributed infrastructure becomes one unified capacity reserve.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Supercharged Inference Performance: No More Months of Manual Tuning
&lt;/h2&gt;

&lt;p&gt;Getting to state-of-the-art (SOTA) inference used to take months of painful performance tuning. GKE is changing that with two solid updates:&lt;/p&gt;

&lt;h3&gt;
  
  
  ML-Driven Predictive Latency Boost
&lt;/h3&gt;

&lt;p&gt;The new feature inside &lt;strong&gt;GKE Inference Gateway&lt;/strong&gt; uses real-time capacity-aware routing to cut &lt;strong&gt;time-to-first-token (TTFT) latency by up to 70%&lt;/strong&gt;. No manual tuning needed. It just works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic KV Cache Storage Tiering
&lt;/h3&gt;

&lt;p&gt;Smart tiering across RAM, Local SSD, and GCS/Lustre helps with long-context memory bottlenecks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offloading to RAM: &lt;strong&gt;40%+ TTFT reduction&lt;/strong&gt; and 50% throughput gain (for 10K prompt length)&lt;/li&gt;
&lt;li&gt;Offloading to Local SSD: &lt;strong&gt;around 70% throughput improvement&lt;/strong&gt; (for 50K prompt length)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features are built on top of &lt;strong&gt;llm-d&lt;/strong&gt;, which just became an official CNCF Sandbox project. GKE also integrates with NVIDIA Dynamo for scaling large Mixture-of-Experts (MoE) models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means for you:&lt;/strong&gt; You can go from deployment to frontier-grade inference in minutes instead of months. That's a huge deal.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Reinforcement Learning Enhancers: Stop Wasting GPU Time
&lt;/h2&gt;

&lt;p&gt;RL is one of the biggest drivers of AI compute demand right now. But the problem is that RL jobs involve a lot of sequential processing, which leaves GPUs and TPUs just sitting idle between steps.&lt;/p&gt;

&lt;p&gt;GKE is adding new native RL capabilities (currently in preview) to fix this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RL Scheduler&lt;/strong&gt; solves the "straggler effect" and inter-batch tail latency, keeping throughput high with intelligent routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RL Sandbox&lt;/strong&gt; gives you kernel-level isolation for tool-calling and reward evaluation at millisecond-scale provisioning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RL Observability and Reliability dashboards&lt;/strong&gt; give you deep visibility out of the box to troubleshoot and optimize the whole RL loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this means for you:&lt;/strong&gt; If you're doing RL training at scale, this is directly targeting the idle-time problem. Less idle time means lower costs. Simple as that.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Intent-Based Autoscaling: No More "Custom Metric Tax"
&lt;/h2&gt;

&lt;p&gt;Scaling AI workloads on anything beyond basic CPU or memory has always been painful. You'd need complex monitoring setups, IAM management, and a dependency on external observability stacks that could fail at the worst moment.&lt;/p&gt;

&lt;p&gt;GKE's new &lt;strong&gt;Intent-Based Autoscaling&lt;/strong&gt; brings native custom metrics support to the Horizontal Pod Autoscaler (HPA):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agentless architecture&lt;/strong&gt; pulls metrics directly from Pods, no external dependencies needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5x faster reaction time&lt;/strong&gt;, dropping from 25 seconds down to just 5 seconds&lt;/li&gt;
&lt;li&gt;If your external observability stack goes down, your autoscaling keeps running. No more cascade failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this means for you:&lt;/strong&gt; This one flies under the radar but it's honestly one of the more impactful updates. Scaling on what actually matters for your workload, not just CPU, is now genuinely easy and reliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Take: One Clear Pattern
&lt;/h2&gt;

&lt;p&gt;Looking at all five of these announcements together, there's a clear theme running through all of them: &lt;strong&gt;Google is making GKE the go-to platform for serious AI workloads&lt;/strong&gt;, not by piling on new features, but by removing the friction that's been slowing developers down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent Sandbox removes the security-vs-performance tradeoff&lt;/li&gt;
&lt;li&gt;Hypercluster removes the cluster fragmentation headache&lt;/li&gt;
&lt;li&gt;Inference optimizations remove months of manual tuning&lt;/li&gt;
&lt;li&gt;RL Enhancers remove GPU idle waste&lt;/li&gt;
&lt;li&gt;Intent-based autoscaling removes fragile external metric dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every single update is about &lt;strong&gt;taking something painful away&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For anyone who's been frustrated running AI workloads on Kubernetes, the Next '26 GKE updates feel like Google finally saying: we get it, and here's the fix.&lt;/p&gt;

&lt;p&gt;Whether you're building multi-agent pipelines, serving frontier models, or doing RL training at scale, these updates are worth paying attention to, especially if cost and performance matter to you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What do you think about these GKE updates? Are you already using any of them or planning to? Drop a comment, I'd love to know what you're building!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>cloudnextchallenge</category>
      <category>googlecloud</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
