<?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 Built a Kubernetes Alternative. It Changed My Perspective on Complexity.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Wed, 27 May 2026 04:50:58 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-built-a-kubernetes-alternative-it-changed-my-perspective-on-complexity-4ja6</link>
      <guid>https://dev.to/kielltampubolon/i-built-a-kubernetes-alternative-it-changed-my-perspective-on-complexity-4ja6</guid>
      <description>&lt;p&gt;After three long nights of coding, I stood on the edge of quitting. The most recent error? 'Connection refused: too many retries.' How does one recover from such a humiliating message? I guess you just pick yourself up and try again.&lt;/p&gt;

&lt;h2&gt;
  
  
  ACT 1: The Confidence Built on Ignorance
&lt;/h2&gt;

&lt;p&gt;At first, I was cocky about my ability to develop a simpler alternative to Kubernetes. After all, I was drawing from over five years of experience building and managing containerized applications. Besides, I had graduated from a reputable programming boot camp, which ended up leading me to some great opportunities. I figured if I could wrangle Kubernetes, I could surely create something less complex and more approachable.&lt;/p&gt;

&lt;p&gt;When I began this project, I believed the only thing standing in my way was time. I had convinced myself that my technical skills were enough; all I had to do was write the code, and it would come together. I thought eliminating a few features and minimizing configurations would be all it took to create something user-friendly. I pictured it, lightweight, no more than 300 lines of code for my entire tool. Simple, right? &lt;/p&gt;

&lt;h2&gt;
  
  
  ACT 2: The Moment It Broke
&lt;/h2&gt;

&lt;p&gt;That illusion shattered when I hit my first major roadblock. After several days of coding with barely any sleep, I tried to deploy my work for the first time. I was so giddy with excitement; it was like I was seeing the light at the end of a tunnel. For about two seconds, it felt like everything was going great. And then came the error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: invalid configuration: failed to find a ready to use service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thought, “How could this happen?” My tool was designed to simplify the deployment process, not make it more convoluted. After sweating over the terminal for hours, I tried everything. I rechecked the API calls, re-evaluated services, and even consulted the Kubernetes official documentation (which, let's be honest, is just a fancy way of saying I ended up down a rabbit hole of YAML files).&lt;/p&gt;

&lt;p&gt;At that moment, it hit me: I hadn’t just omitted complexity; I had disregarded the very principles that make Kubernetes powerful, its ability to manage the scalability and orchestration of applications in distributed environments. I realized simplicity doesn't always mean fewer lines of code. Sometimes, it means better abstractions that serve higher purposes. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point: What I Discovered
&lt;/h2&gt;

&lt;p&gt;I decided to roll my sleeves up, go back to the drawing board, and analyze what I was actually trying to achieve. Instead of just cutting features, I began to rethink some of them entirely. I analyzed critical aspects like service discovery and load balancing and realized that perhaps they weren’t&lt;/p&gt;

</description>
      <category>devops</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>github</category>
    </item>
    <item>
      <title>I Dumped My Entire Codebase into Gemma 4. It Found a Bug My Team Missed for 3 Months.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Mon, 18 May 2026 13:54:12 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-dumped-my-entire-codebase-into-gemma-4-it-found-a-bug-my-team-missed-for-3-months-3oa1</link>
      <guid>https://dev.to/kielltampubolon/i-dumped-my-entire-codebase-into-gemma-4-it-found-a-bug-my-team-missed-for-3-months-3oa1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Write About Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The bug had been in production for 90 days. Three sprint reviews. Two refactors nearby. One team member who actually touched that file and left a comment saying "looks fine."&lt;/p&gt;

&lt;p&gt;I found it in 4 minutes by pasting 47,000 tokens into Gemma 4 31B and asking one question.&lt;/p&gt;

&lt;p&gt;I want to tell you this is a story about AI being brilliant. It is not. It is a story about what happens when you stop treating a 128K context window as a spec sheet number and start treating it as a tool.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick context if you're new to Gemma 4:&lt;/strong&gt; It's Google DeepMind's latest open-weight model family, released April 2026 under Apache 2.0. The 31B dense variant is the largest in the family and the one that ships with a 128K context window and built-in reasoning mode. You can access it free via &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; with no credit card required.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Bug Was Invisible at File Level
&lt;/h2&gt;

&lt;p&gt;We run a mid-sized Python backend. Not a startup toy project, not a monolith from 2009 either. Around 18,000 lines across 200-ish files, with a payment reconciliation module that processes about 4,000 transactions a day.&lt;/p&gt;

&lt;p&gt;For three months we had an off-by-one error in how we accumulated daily totals. The kind of bug that doesn't crash anything, doesn't throw an exception, and stays quiet until someone runs a quarterly audit. Finance flagged it. Our first assumption was the database. Our second assumption was a race condition. Our third assumption was a rounding issue inherited from a third-party SDK.&lt;/p&gt;

&lt;p&gt;All wrong. The bug was in how we correlated timestamps across two functions that lived 800 lines apart in the same module. You could not see it by reading either function. You had to hold both in your head at the same time, along with the utility function they both relied on, to see the logic drift.&lt;/p&gt;

&lt;p&gt;Nobody held all three at once. Humans don't naturally do that across 800 lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Did
&lt;/h2&gt;

&lt;p&gt;I pulled the relevant module (about 1,400 lines), the shared utilities it imports (another 600 lines), and the test file (300 lines). Total: roughly 2,300 lines of Python. That's somewhere around 46,000 tokens.&lt;/p&gt;

&lt;p&gt;I chose Gemma 4 31B, accessed via OpenRouter, specifically because of the context window size and the reasoning capability. The 128K window was not the point in itself. The point was that 128K let me stop chunking.&lt;/p&gt;

&lt;p&gt;Every time I had tried to debug this with smaller-context tools I was feeding pieces of the problem. Paste function A, get feedback. Paste function B, get different feedback. Try to synthesize the two answers manually. That synthesis step is where the bug kept hiding.&lt;/p&gt;

&lt;p&gt;With 31B I pasted the full thing at once:&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="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&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;
You are reviewing Python source code for a financial reconciliation system.
Below is the complete module, its utility dependencies, and its test suite.

Find any logic errors related to timestamp handling, accumulation order,
or correlation between functions. Be specific about line numbers and
explain the interaction that causes the error, not just the error itself.

&amp;lt;module&amp;gt;
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reconciliation_module&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;/module&amp;gt;

&amp;lt;utilities&amp;gt;
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shared_utils&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;/utilities&amp;gt;

&amp;lt;tests&amp;gt;
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_suite&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;/tests&amp;gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No chunking. No iterative back-and-forth. One completion.&lt;/p&gt;

&lt;p&gt;The response came back in about 40 seconds. It flagged three things. Two were style notes I already knew about. The third was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;accumulate_daily()&lt;/code&gt; uses &lt;code&gt;record.created_at&lt;/code&gt; for grouping (line 312), but &lt;code&gt;reconcile_period()&lt;/code&gt; filters by &lt;code&gt;record.settled_at&lt;/code&gt; (line 1089). For records that settle in a different calendar day than they were created, these two functions will assign the same record to different days. The test suite only creates records where &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;settled_at&lt;/code&gt; are within the same UTC day, so this has never been caught by the test harness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I sat there for a second. Then I checked the two functions side by side:&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;# line 312 — in accumulate_daily()
&lt;/span&gt;&lt;span class="n"&gt;day_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# &amp;lt;-- groups by creation date
&lt;/span&gt;
&lt;span class="c1"&gt;# line 1089 — in reconcile_period()
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settled_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;period_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# &amp;lt;-- filters by settlement date
&lt;/span&gt;    &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day_key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a transaction is created on Monday but settles on Tuesday, &lt;code&gt;accumulate_daily()&lt;/code&gt; puts it in Monday's bucket. &lt;code&gt;reconcile_period()&lt;/code&gt; counts it toward Tuesday's filter window. Same record. Two different days. Neither function is wrong on its own. Together they silently drift.&lt;/p&gt;

&lt;p&gt;Then I checked the test fixtures. Every single test record had &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;settled_at&lt;/code&gt; set to the same UTC day. The tests never exercised the cross-day case.&lt;/p&gt;

&lt;p&gt;It was exactly right.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Worked and Why It Didn't Work Before
&lt;/h2&gt;

&lt;p&gt;The test suite was the key detail. The model wasn't just spotting a timestamp mismatch between two functions. It was reading the tests and noticing the gap: the tests don't cover the case where these dates differ. That kind of cross-file reasoning requires the full context to be present simultaneously.&lt;/p&gt;

&lt;p&gt;I had tried a similar experiment with GPT-4 earlier in the week by pasting sections iteratively. It flagged the &lt;code&gt;created_at&lt;/code&gt; vs &lt;code&gt;settled_at&lt;/code&gt; discrepancy in isolation but didn't make the connection to the test fixtures because the test file wasn't in scope when it saw the functions. The insight was split across two separate completions, and I didn't connect them.&lt;/p&gt;

&lt;p&gt;With the full module in one pass, the model could see the claim ("here is how we accumulate") and the verification ("here is what the tests assert") in the same window, and notice they didn't match on the cross-day case.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Limits
&lt;/h2&gt;

&lt;p&gt;This is not a "just paste your codebase and it will find all your bugs" story. Three caveats that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency is real.&lt;/strong&gt; The 31B model via OpenRouter takes time on a 46K token completion. For quick questions I still reach for smaller models or a local Gemma 4 E4B setup. The 31B is the right tool when you need the full picture and can afford the wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A vague prompt gets vague answers.&lt;/strong&gt; My first attempt asked "find any issues." It returned generic style feedback. The version that found the bug asked specifically for "logic errors related to timestamp handling" because I had already narrowed the domain. The model amplified my hypothesis. It did not generate one from nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It still missed one bug.&lt;/strong&gt; I found it later through manual review. Not a humiliating miss. But a reminder that "found the bug I missed" and "found all the bugs" are very different sentences.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What This Actually Changes
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The 128K context window is not about being able to process more. It is about being able to stop decomposing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most debugging assistance with AI is a decomposition problem: I have to decide what to show and what to leave out, and that editorial decision is exactly where bugs hide. The moment I have to decide "this function is probably not relevant," I might be wrong about that, and the model will never tell me.&lt;/p&gt;

&lt;p&gt;When the window is large enough that I can stop curating, the model sees the same thing I should be seeing: the whole picture, including the parts I didn't think to highlight.&lt;/p&gt;

&lt;p&gt;That changes the failure mode from "I showed it the wrong piece" to "I asked the wrong question." The second failure mode is actually fixable.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it yourself:&lt;/strong&gt; grab a module you've been meaning to audit, load the full file plus its tests into Gemma 4 31B on &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; (free tier, no card), and ask specifically about the interaction between the functions, not just each function alone. Drop what you find in the comments. I'm genuinely curious whether the cross-file blindspot is a universal problem or just a symptom of how our specific codebase grew.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Was Wrong About AI. Here Is the Moment That Changed It.</title>
      <dc:creator>Kiell Tampubolon</dc:creator>
      <pubDate>Mon, 18 May 2026 13:30:36 +0000</pubDate>
      <link>https://dev.to/kielltampubolon/i-was-wrong-about-ai-here-is-the-moment-that-changed-it-1cb</link>
      <guid>https://dev.to/kielltampubolon/i-was-wrong-about-ai-here-is-the-moment-that-changed-it-1cb</guid>
      <description>&lt;p&gt;The debugging tool flagged a staggering 150 issues in my code almost instantly. I was astonished by how many mistakes I had made and how far I still had to go. This moment revealed the complexity of AI that I had underestimated all along.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment It Broke
&lt;/h2&gt;

&lt;p&gt;That breaking point was unexpected. I was toying with a fancy AI algorithm, thinking it was about to churn out perfect code. I had linked it with my code editor, set everything up, and watched with excitement as it typed out solutions based on prompts I fed it. After a few successful iterations, I made a huge mistake. I forgot to properly validate the inputs. &lt;/p&gt;

&lt;p&gt;One afternoon, I threw in a random input to test its limits. The console displayed the message that will haunt me: "Runtime Error: Unexpected Token."&lt;/p&gt;

&lt;p&gt;For a moment, I was frozen. I had ignored something critical: AI is a tool, not a solution in itself. More often than not, I’d been treating it as some kind of oracle instead of evaluating how it actually understood my requests. I should’ve known better. Sure, AI can assist in many ways, but nothing beats core programming principles.&lt;/p&gt;

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

&lt;p&gt;When I finally debugged the mess, I took a moment to reflect. I realized that I had neglected proper coding best practices in favor of a shiny new toy. AI works wonderfully when it augments your existing understanding and workflow. Here’s a snippet demonstrating the change I made:&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;// Original flawed function without validation&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;aiSuggestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;aiModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Improved function with validation&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;aiSuggestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;Invalid input: Please provide a valid string.&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="nx"&gt;aiModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;Just adding that validation step made a world of difference. I could trust my AI’s feedback more because I was guiding it with better input. It was a simple tweak, but it became pivotal in my project’s success. I still smiled when my AI echoed back code that was far more functional than my initial attempts, but now I was equipped with the knowledge that I needed to do my part first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Principle
&lt;/h2&gt;

&lt;p&gt;This lead to a bigger realization: tools are just extensions of ourselves. They don't replace the fundamentals. If you're a developer working with AI, you have to take responsibility for your code. Forgetting that turns you into a passive user, and honestly, nobody wants to be that. It’s like trying to build a house without knowing how to lay bricks; the walls might look good for a while, but they’re bound to crumble eventually.&lt;/p&gt;

&lt;p&gt;When I look back, what annoys me most is that I didn’t question my assumptions sooner. I could’ve saved time, energy, and probably a few hairs on my head. Relying solely on AI to deliver the goods is tempting, but it leads to dangerous shortcuts. Proper coding practices don’t just lead to better outcomes; they prevent mistakes down the road.&lt;/p&gt;

&lt;p&gt;In hindsight, I’d tell past-me to challenge the narrative that tech can solve everything. AI should elevate our skills, not replace them. So here’s my burning question: Are AI and automation a developer's best support system, or do they create a dangerous dependency that might weaken our core skills? What’s your take on this?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>javascript</category>
      <category>cloudflare</category>
    </item>
    <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>
  </channel>
</rss>
