<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged debugging</title>
<link>https://coderlegion.com/tag/debugging</link>
<description>Powered by Question2Answer</description>
<item>
<title>The Semicolon That Stole 5 Days of My Life</title>
<link>https://coderlegion.com/33206/the-semicolon-that-stole-5-days-of-my-life</link>
<description>&lt;p&gt;I have never been this frustrated with a single bug in my life.&lt;/p&gt;
&lt;p&gt;And the culprit? The classic, infamous semicolon.&lt;/p&gt;
&lt;p&gt;I recently started exploring Express.js and wired up a MongoDB database for an assignment. Connecting the database was a small hurdle, but that wasn’t the real fight.&lt;br&gt;
The terminal said the database was connected. But Postman and the browser? Total silence. No response, no error.&lt;/p&gt;
&lt;p&gt;I created a fresh test database. Nothing. Then I disconnected the database entirely and bypassed my .env file, and suddenly the routes worked.&lt;/p&gt;
&lt;p&gt;So my brain locked onto the wrong villain: “The URI must be broken.” “Maybe it’s the network whitelist.” “Maybe the connection string is formatted wrong.”&lt;/p&gt;
&lt;p&gt;I spent 5 days down that rabbit hole.&lt;/p&gt;
&lt;p&gt;This morning, with the submission deadline today, I told myself: if I don’t fix this now, I’m deleting the whole folder and starting over. So I opened every file one last time.&lt;br&gt;
And there it was, hiding in plain sight in my .env file:&lt;/p&gt;
&lt;p&gt;PORT=3000;&lt;/p&gt;
&lt;p&gt;In JavaScript, a semicolon ends a statement. In a .env file, it’s just part of the value. My port wasn’t 3000, it was “3000;”, so the server never listened where I was sending requests.&lt;/p&gt;
&lt;p&gt;Five days. One character.&lt;/p&gt;
&lt;p&gt;I still can’t decide whether to be relieved that it works or annoyed that a single semicolon stole my peace for a week. But this is part of what we signed up for as developers. The wins are great, and the silent syntax traps are real.&lt;/p&gt;
&lt;p&gt;No time to celebrate though. The deadline is today.&lt;/p&gt;
&lt;p&gt;What’s the most ridiculous bug that ate days of your life for no good reason? I want to hear your horror stories.&lt;/p&gt;
&lt;p&gt;Adiós.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33206/the-semicolon-that-stole-5-days-of-my-life</guid>
<pubDate>Sat, 31 Oct 2026 08:21:59 +0000</pubDate>
</item>
<item>
<title>WordPress Crash Recovery &amp; Plugin Conflict Resolution</title>
<link>https://coderlegion.com/33093/wordpress-crash-recovery-plugin-conflict-resolution</link>
<description>&lt;p&gt;When a WordPress site crashes immediately after an update, the immediate business impact is lost revenue, disrupted customer workflows, and compromised search standing. In production environments, standard advice like &quot;restore a backup from yesterday&quot; can cause critical data loss: orders placed between the backup and the crash, customer registrations, and recent transactions vanish.&lt;/p&gt;
&lt;p&gt;A disciplined engineering approach treats a crashed WordPress site as a contained system failure: isolate the execution failure, restore availability, fix the root cause, and verify data integrity before returning traffic to normal operations.&lt;/p&gt;
&lt;h3 id=&quot;common-crash-failure-modes-after-updates&quot;&gt;Common Crash Failure Modes After Updates&lt;/h3&gt;
&lt;p&gt;WordPress crashes after updates typically stem from four discrete architectural failure modes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;PHP Fatal Errors (Type Errors &amp;amp; Deprecations)&lt;/strong&gt;: A plugin update requires PHP 8.1+ syntax or library extensions that conflict with the server runtime or an older theme calling deprecated functions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hook and Filter Signature Conflicts&lt;/strong&gt;: Two active plugins hook into the same action (e.g., &lt;code&gt;woocommerce_checkout_order_processed&lt;/code&gt; or &lt;code&gt;template_redirect&lt;/code&gt;) expecting incompatible argument types or returning invalid objects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database Schema Migrations &amp;amp; Locked Tables&lt;/strong&gt;: Complex plugins (WooCommerce, membership tools, custom post-type managers) execute DDL migrations upon activation. If a migration times out or hits MySQL lock contention, queries fail across the application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autoloaded Options &amp;amp; Memory Exhaustion&lt;/strong&gt;: Plugins accumulating transient data in &lt;code&gt;wp_options&lt;/code&gt; with &lt;code&gt;autoload = 'yes'&lt;/code&gt; spike PHP memory usage beyond &lt;code&gt;memory_limit&lt;/code&gt;, triggering fatal out-of-memory crashes on high-concurrency requests.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;systematic-diagnostic-protocol&quot;&gt;Systematic Diagnostic Protocol&lt;/h3&gt;
&lt;p&gt;When diagnosing a crashed WordPress production instance, follow this 5-step engineering triage:&lt;/p&gt;
&lt;h4 id=&quot;step-1-capture-raw-error-logs-without-exposing-stack-traces&quot;&gt;Step 1: Capture Raw Error Logs Without Exposing Stack Traces&lt;/h4&gt;
&lt;p&gt;Do not enable &lt;code&gt;WP_DEBUG_DISPLAY&lt;/code&gt; on a live production URL. Instead, enable debug logging to a private log destination:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// In wp-config.php (before /* That's all, stop editing! */)
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', '/var/log/wordpress/debug.log'); // Secure path outside public web root
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', '0');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect &lt;code&gt;debug.log&lt;/code&gt; or the web server error log (NGINX/Apache/FPM error log) to identify the exact file, line number, and exception stack trace.&lt;/p&gt;
&lt;h4 id=&quot;step-2-safe-plugin-isolation-via-wp-cli&quot;&gt;Step 2: Safe Plugin Isolation via WP-CLI&lt;/h4&gt;
&lt;p&gt;Avoid renaming the entire &lt;code&gt;wp-content/plugins&lt;/code&gt; folder if possible, as this deactivates all plugins and breaks widget and database mappings upon reactivation. Use WP-CLI to isolate the culprit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Check status of installed plugins
wp plugin list --status=active

# Deactivate the suspect plugin identified in the error stack trace
wp plugin deactivate offending-plugin-slug --skip-plugins --skip-themes

# Verify frontend HTTP response code
curl -I https://example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-3-check-mysql-database-integrity-and-migration-locks&quot;&gt;Step 3: Check MySQL Database Integrity and Migration Locks&lt;/h4&gt;
&lt;p&gt;If the error indicates missing tables or broken queries, verify table health:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Check and repair WordPress core and plugin tables
wp db check
wp db repair

# Identify oversized autoloaded options causing memory exhaustion
wp db query &quot;SELECT option_name, LENGTH(option_value) AS size FROM wp_options WHERE autoload='yes' ORDER BY size DESC LIMIT 10;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-4-patch-compatibility-or-pin-version&quot;&gt;Step 4: Patch Compatibility or Pin Version&lt;/h4&gt;
&lt;p&gt;If the site requires the updated plugin for business operations, evaluate whether a targeted patch (e.g., handling a null object check in a template hook) or a clean version downgrade via WP-CLI is appropriate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Safely roll back to previous known-stable version
wp plugin install offending-plugin-slug --version=2.4.1 --force
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-5-verification-and-regression-testing&quot;&gt;Step 5: Verification and Regression Testing&lt;/h4&gt;
&lt;p&gt;Verify that critical customer journeys work end-to-end:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Homepage, archive, and single post rendering&lt;/li&gt;
&lt;li&gt;User authentication and session persistence&lt;/li&gt;
&lt;li&gt;E-commerce cart, checkout, and webhook callbacks&lt;/li&gt;
&lt;li&gt;Scheduled cron tasks (&lt;code&gt;wp cron event list&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;when-to-seek-engineering-assistance&quot;&gt;When to Seek Engineering Assistance&lt;/h3&gt;
&lt;p&gt;If a WordPress crash involves corrupted database transactions, custom plugin architecture conflicts, security compromises, or legacy codebase friction, contained engineering intervention restores stability safely.&lt;/p&gt;
&lt;p&gt;For rapid assistance with an active failure or production regression, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://sazm.in/bug-fix-services&quot;&gt;WordPress &amp;amp; website bug fixing&lt;/a&gt; or queue an &lt;a rel=&quot;nofollow&quot; href=&quot;https://sazm.in/checkout/emergency&quot;&gt;urgent bug fix&lt;/a&gt;. To prevent update regressions entirely with weekly staging tests and daily backups, explore the &lt;a rel=&quot;nofollow&quot; href=&quot;https://sazm.in/wordpress-care&quot;&gt;WordPress Care Plan&lt;/a&gt;. If your platform requires broader architecture stabilization, review SazM's &lt;a rel=&quot;nofollow&quot; href=&quot;https://sazm.in/audit&quot;&gt;Diagnostic Audits&lt;/a&gt; or start a direct technical inquiry at &lt;a rel=&quot;nofollow&quot; href=&quot;https://sazm.in/contact&quot;&gt;the contact page&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published with full diagnostic protocols at &lt;a rel=&quot;nofollow&quot; href=&quot;https://sazm.in/articles/wordpress-crash-recovery-after-plugin-update&quot;&gt;SazM: WordPress Crash Recovery &amp;amp; Plugin Conflict Resolution&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33093/wordpress-crash-recovery-plugin-conflict-resolution</guid>
<pubDate>Fri, 30 Oct 2026 14:36:25 +0000</pubDate>
</item>
<item>
<title>I Solved the Same Gmail Problem Two Ways Without Noticing</title>
<link>https://coderlegion.com/32978/i-solved-the-same-gmail-problem-two-ways-without-noticing</link>
<description>&lt;p&gt;This is the third post from my n8n intake classifier, a practice project for a fictional automation agency that I built with an AI collaborator. The first was about &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/31215/each-fix-to-my-classification-prompt-exposed-the-next-problem&quot;&gt;the classification prompt&lt;/a&gt;. The second was about &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/32063/ambiguous-invalid-or-system-error&quot;&gt;ambiguous, invalid and system_error&lt;/a&gt;. This one is about the wiring after classification: where logging goes, what the Gmail node does to your data, and a bug I only found by opening real inboxes. The workflow is in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Tarek-Elzoghby/client-intake-classifier&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A quick orientation: n8n is a workflow automation tool, and a workflow is built from nodes, each one a single step. The ones that matter here are Switch (sends an item down one of several paths), Set (reshapes the data) and Gmail (sends an email).&lt;/p&gt;
&lt;h3 id=&quot;where-logging-was-supposed-to-go&quot;&gt;Where logging was supposed to go&lt;/h3&gt;
&lt;p&gt;After classification, a Switch node sends each submission down one of five paths, and each path sends emails. Every submission also gets logged to Google Sheets. For this phase I planned to work the way I had in the earlier ones: Sheets first, then emails, one branch at a time.&lt;/p&gt;
&lt;p&gt;Before building anything, I caught a conflict. Logging is the same logic on all five paths, and only the data changes. Putting it first inside each branch meant five copies of one node, or converging after Sheets and then branching again to send category-specific emails. Neither made sense. So I flipped the order: build the emails first, since those differ per category, and let all five paths converge on one shared Sheets node at the end.&lt;/p&gt;
&lt;h3 id=&quot;gmail-replaces-your-data&quot;&gt;Gmail replaces your data&lt;/h3&gt;
&lt;p&gt;The first branch I built showed a second problem. When the Gmail node sends an email, the item it passes on is Gmail's own send confirmation, not the submission. Name, Email and description were gone by the time anything downstream needed them. The LLM Chain node had done the same thing to me earlier.&lt;/p&gt;
&lt;p&gt;In the ambiguous/system_error branch, I added a Set node right after the internal notification to rebuild one clean object. The IF node and both client emails read from that, instead of each reaching back to whichever earlier node had the data. In simple_automation, I added a Set node after the client email to rebuild the object before it reached the shared Sheets node.&lt;/p&gt;
&lt;h3 id=&quot;noticing-i-d-solved-it-twice&quot;&gt;Noticing I'd solved it twice&lt;/h3&gt;
&lt;p&gt;While wiring the final Sheets node, I looked at the ambiguous/system_error branch and saw it fed Sheets from two points off the same rebuild node, one path through the client emails and one straight to Sheets. That wasn't a bug. The email path never reconnected to Sheets, so nothing was logged twice. But it showed me I had solved the Gmail problem two different ways. Everywhere else I rebuilt the data after the email. In this branch I branched before the email, because Sheets never needed anything the email step produced.&lt;/p&gt;
&lt;h3 id=&quot;moving-logging-upstream&quot;&gt;Moving logging upstream&lt;/h3&gt;
&lt;p&gt;That comparison pointed at something bigger. Every branch converged on one Sheets node, so there was no reason logging should depend on any email succeeding. A Gmail send can fail for reasons that have nothing to do with the submission.&lt;/p&gt;
&lt;p&gt;I moved the Sheets connection to the two points right after classification: one for a successful classification and one for the failure handler. Both now feed the Switch and Sheets in parallel. Two side effects followed. &lt;code&gt;has_partial_intent&lt;/code&gt; only had to be added in two places instead of four, and I could delete the end-of-branch Set nodes that existed only to feed the old Sheets wiring.&lt;/p&gt;
&lt;p&gt;Before deleting any of them, I checked each one. Some still fed a client email's own data, and removing one of those would have broken a client-facing email without touching Sheets. I kept those.&lt;/p&gt;
&lt;p&gt;The final build still contains both patterns. The ambiguous/system_error branch has its rebuild node, and the other client emails reach back to earlier nodes by name:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;={{ $('Route: By Classification Category').item.json.Email }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I haven't compared whether one pattern is more robust than the other. What changed is that logging no longer depends on either.&lt;/p&gt;
&lt;h3 id=&quot;checking-real-inboxes&quot;&gt;Checking real inboxes&lt;/h3&gt;
&lt;p&gt;After the rewiring, I retested every branch individually and checked real inboxes, not just node output. That caught a bug in the ai_workflow branch: the internal notification and the client email had their destinations swapped. The internal-only message went to the client, and the client's confirmation went to the team.&lt;/p&gt;
&lt;p&gt;Both emails sent without errors, so nothing had failed that would make me suspect a problem. Only who received what showed it. I then confirmed the simple_automation branch had no swap, since I'd used the same inbox check there.&lt;/p&gt;
&lt;p&gt;My notes don't record how the swap got there, or whether the rewiring caused it, so I won't guess. The current build sends each internal email to the team address and each client email to the submitter.&lt;/p&gt;
&lt;h3 id=&quot;a-test-case-i-wrote-wrong&quot;&gt;A test case I wrote wrong&lt;/h3&gt;
&lt;p&gt;While testing &lt;code&gt;ambiguous&lt;/code&gt;, my first case was a deliberately vague description. It came back as &lt;code&gt;invalid&lt;/code&gt;, and I took that as a bug. Reading the reasoning showed the classifier was right. Vague wording alone doesn't give any category a specific signal to rest on, which is a different situation from two categories each being concretely supported. I wrote a sharper case with explicit signals for both &lt;code&gt;ai_workflow&lt;/code&gt; and &lt;code&gt;custom_integration&lt;/code&gt;, and that one returned &lt;code&gt;ambiguous&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-this-doesn-t-show&quot;&gt;What this doesn't show&lt;/h3&gt;
&lt;p&gt;Every test used descriptions I wrote. A forced classification failure does produce a row in the sheet, with the &lt;code&gt;has_partial_intent&lt;/code&gt; cell empty, because the model never produced a value on that path. I did not test what happens if the Sheets step itself fails.&lt;/p&gt;
&lt;p&gt;What I take from it is narrow: logging shouldn't wait on anything that can fail for unrelated reasons, and a branch that sends emails isn't tested until I've read the emails.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32978/i-solved-the-same-gmail-problem-two-ways-without-noticing</guid>
<pubDate>Thu, 29 Oct 2026 13:52:12 +0000</pubDate>
</item>
<item>
<title>PS4 Pro Belize DDR3 patch v2</title>
<link>https://coderlegion.com/32257/ps4-pro-belize-ddr3-patch-v2</link>
<description>&lt;p&gt;The last time the DDR3 memory on PS4 pro has a critical problem it was capped under 11mb/s speed how much I tied I failed every time &lt;/p&gt;
&lt;p&gt;then I notice something while debugging through the southbridge well it turns out DDR3 has it's own southbridge DMA engine but I don't really notice at first.&lt;/p&gt;
&lt;p&gt;and after some more debugging it turns out my DDR3 chip &lt;em&gt;(1 GB on the southbridge)&lt;/em&gt; was being read by the CPU personally, 8 bytes at a time, through a tiny PCI window ~10.6 MB/s. and that was the main bottleneck of all.&lt;/p&gt;
&lt;p&gt;now it feels like I almost fix the issue but nope the southbridge DMA engine has no Linux driver for PS4 to use it.&lt;/p&gt;
&lt;p&gt;and after more debugging  it turns out Sony's PS4 gameOS aka orbis uses that chip (their sbram driver), but the code is closed. &lt;/p&gt;
&lt;p&gt;So, I used AI to reverse engineer the kernel binary and everything started making sense now&lt;/p&gt;
&lt;p&gt; &lt;em&gt;the chip lives at 0xC0000000 + offset on the internal bus and everything moves through the southbridge DMA engine with exact protocol byte-count, source, dest, then the 0x1d40 &quot;go&quot; register, and write 0x11 to the interrupt-mask register.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;after that it's time to do the main patch The implementation&lt;/p&gt;
&lt;p&gt;On the ps4linux it sees the southbridge DMA engine as a pci device: 104d:90cd. The driver asks linux for it, switches it on, and maps its register memory. Now the engine can't touch program memory directly. everything goes through one special buffer the driver allocates at startup a small chunk of RAM that is always below 4 GB and always coherent (DMA-visible) think of it as the loading dock between the CPU and the DDR3.&lt;/p&gt;
&lt;p&gt;After the patch bake into the kernel and the result &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;old patch &lt;/th&gt;&lt;th&gt;new patch&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;reads   &lt;/td&gt;&lt;td&gt;10.6 MB/s &lt;/td&gt;&lt;td&gt;&lt;strong&gt;504 MB/s&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;writes  &lt;/td&gt;&lt;td&gt;~138 MB/s &lt;/td&gt;&lt;td&gt;&lt;strong&gt;425 MB/s&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ceiling &lt;/td&gt;&lt;td&gt;unknown   &lt;/td&gt;&lt;td&gt;&lt;strong&gt;~1.4 GB/s&lt;/strong&gt; (65,408 B jobs @ 1,383 MB/s)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;The engine itself moves data at up to ~1.4 GB/s. what used to be a 10.6 MB/s unusable brick is now a fast little swap chip on PS4linux&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5jtjwqvdjlnpidtmpgn6.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32257/ps4-pro-belize-ddr3-patch-v2</guid>
<pubDate>Sat, 24 Oct 2026 08:36:00 +0000</pubDate>
</item>
<item>
<title>How to Fix Metro Bundler Stuck or Not Starting Errors in React Native</title>
<link>https://coderlegion.com/32069/how-to-fix-metro-bundler-stuck-or-not-starting-errors-in-react-native</link>
<description>&lt;p&gt;If you've worked with React Native for a while, you've probably run into a problem where Metro refuses to start, gets stuck while bundling, or leaves your app waiting for JavaScript that never arrives.&lt;/p&gt;
&lt;p&gt;Sometimes the terminal shows an error immediately. Other times, Metro appears to start normally, but the app stays on a loading screen or fails to connect to the development server.&lt;/p&gt;
&lt;p&gt;The frustrating part is that these symptoms can have completely different causes. A port conflict, stale cache, broken dependency installation, invalid configuration, or network issue can all look like a Metro problem.&lt;/p&gt;
&lt;p&gt;Here's how to narrow it down and fix the actual cause instead of repeatedly restarting your project and hoping something changes.&lt;/p&gt;
&lt;h3 id=&quot;1-identify-what-metro-is-actually-doing&quot;&gt;1. Identify What Metro Is Actually Doing&lt;/h3&gt;
&lt;p&gt;Before changing anything, identify which of these situations matches your problem.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metro won't start:&lt;/strong&gt; The development server exits with an error, refuses to launch, or reports that a port is already in use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metro starts but gets stuck bundling:&lt;/strong&gt; The terminal shows that bundling has started, but it never finishes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The app cannot connect to Metro:&lt;/strong&gt; The server appears to be running, but the application displays a loading screen or a connection error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metro starts, but the app crashes:&lt;/strong&gt; Bundling may be successful, but a JavaScript exception or native configuration problem prevents the app from running.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are different failure points. Clearing the cache might help with a stale bundle, but it won't fix a port occupied by another process or an invalid native configuration.&lt;/p&gt;
&lt;p&gt;Start with the simplest checks and move toward more disruptive fixes only when necessary.&lt;/p&gt;
&lt;h3 id=&quot;2-stop-metro-and-start-it-again&quot;&gt;2. Stop Metro and Start It Again&lt;/h3&gt;
&lt;p&gt;First, stop any Metro server running in the terminal by pressing &lt;code&gt;Ctrl + C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then start it using the command appropriate for your project.&lt;/p&gt;
&lt;h4 id=&quot;if-you-re-using-expo&quot;&gt;If you're using Expo&lt;/h4&gt;
&lt;p&gt;From your project directory, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To clear Expo's Metro cache at the same time, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start --clear
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;if-you-re-using-react-native-cli&quot;&gt;If you're using React Native CLI&lt;/h4&gt;
&lt;p&gt;Start Metro from your project's root directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you suspect stale cached files, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native start --reset-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You don't need to clear the cache every time Metro fails. Start with the normal command. Use the cache-reset option when the symptoms suggest stale or inconsistent cached data, or when a normal restart doesn't help.&lt;/p&gt;
&lt;p&gt;If Metro exits immediately, read the terminal output before trying another command. The first meaningful error is often more useful than the final stack trace.&lt;/p&gt;
&lt;h3 id=&quot;3-check-whether-port-8081-is-already-in-use&quot;&gt;3. Check Whether Port 8081 Is Already in Use&lt;/h3&gt;
&lt;p&gt;Metro normally uses port &lt;code&gt;8081&lt;/code&gt;. If another process is listening on that port, Metro may fail to start or ask you to use a different one.&lt;/p&gt;
&lt;p&gt;The terminal might show a message similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Error: listen EADDRINUSE: address already in use :::8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means another process is already using the requested address and port.&lt;/p&gt;
&lt;h4 id=&quot;on-windows&quot;&gt;On Windows&lt;/h4&gt;
&lt;p&gt;Open Command Prompt and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;netstat -ano | findstr :8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for a line containing &lt;code&gt;LISTENING&lt;/code&gt;. The last column contains the process ID (PID).&lt;/p&gt;
&lt;p&gt;You can identify the process with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;tasklist /FI &quot;PID eq 12345&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;12345&lt;/code&gt; with the actual PID.&lt;/p&gt;
&lt;p&gt;If you've confirmed that the process is an old Metro server you no longer need, you can stop it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;taskkill /PID 12345 /F
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;on-macos-or-linux&quot;&gt;On macOS or Linux&lt;/h4&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsof -i :8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect the process listed for port &lt;code&gt;8081&lt;/code&gt;. If it's an old Metro process that you no longer need, stop that specific process:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kill &amp;lt;PID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;lt;PID&amp;gt;&lt;/code&gt; with the actual process ID.&lt;/p&gt;
&lt;p&gt;Don't terminate a process simply because it's using the port. Confirm what it is first, especially if other development tools are running.&lt;/p&gt;
&lt;p&gt;Once the port is available, start Metro again.&lt;/p&gt;
&lt;h3 id=&quot;4-clear-metro-s-cache-when-bundling-gets-stuck&quot;&gt;4. Clear Metro's Cache When Bundling Gets Stuck&lt;/h3&gt;
&lt;p&gt;Metro transforms your JavaScript files and their dependencies into a bundle the application can execute. Cached transformation results help make subsequent builds faster, but stale or inconsistent cached data can sometimes cause confusing behavior.&lt;/p&gt;
&lt;p&gt;Typical symptoms include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bundling worked before, but now fails after code or dependency changes.&lt;/li&gt;
&lt;li&gt;The app appears to use an older version of your JavaScript.&lt;/li&gt;
&lt;li&gt;Metro repeatedly fails while transforming files without an obvious dependency error.&lt;/li&gt;
&lt;li&gt;A normal restart doesn't resolve the problem.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try the appropriate cache-reset command from Step 2.&lt;/p&gt;
&lt;p&gt;After the cache is cleared, let Metro rebuild the bundle. The first build can take longer because the cached transformation results need to be regenerated.&lt;/p&gt;
&lt;p&gt;If the same error returns, don't keep clearing the cache. Look at the specific file, dependency, or configuration mentioned in the terminal output. A repeatable error usually means there's something more specific to fix.&lt;/p&gt;
&lt;h3 id=&quot;5-check-your-dependencies-before-reinstalling-everything&quot;&gt;5. Check Your Dependencies Before Reinstalling Everything&lt;/h3&gt;
&lt;p&gt;Metro depends on the packages and configuration in your React Native project. If a package installation was interrupted, dependencies were changed, or incompatible versions were introduced, Metro may fail while loading its configuration or resolving modules.&lt;/p&gt;
&lt;p&gt;Start by checking the actual error.&lt;/p&gt;
&lt;p&gt;For example, these messages point toward different problems:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cannot find module ...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cannot find module 'metro-config'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A missing module doesn't automatically mean the entire dependency installation is broken. It might be an incorrect import path, a package that was never installed, or a version mismatch.&lt;/p&gt;
&lt;p&gt;Check the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Did the problem start immediately after installing, removing, or updating a package?&lt;/li&gt;
&lt;li&gt;Does the error identify a particular module or package?&lt;/li&gt;
&lt;li&gt;Does your project use the package versions expected by its React Native or Expo version?&lt;/li&gt;
&lt;li&gt;Did you accidentally run the installation command from the wrong directory?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the project uses npm and has a valid, committed &lt;code&gt;package-lock.json&lt;/code&gt;, a clean installation may be appropriate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reinstalls dependencies according to the lockfile and removes the existing &lt;code&gt;node_modules&lt;/code&gt; directory as part of the process.&lt;/p&gt;
&lt;p&gt;For a Yarn project, use the installation command appropriate for its Yarn version and existing lockfile. Don't switch package managers or delete your lockfile just to see whether Metro starts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avoid deleting &lt;code&gt;node_modules&lt;/code&gt;, lockfiles, and caches all at once as your first response.&lt;/strong&gt; Doing so can introduce new version differences and make it harder to identify what originally caused the failure.&lt;/p&gt;
&lt;h3 id=&quot;6-check-metro-and-expo-configuration&quot;&gt;6. Check Metro and Expo Configuration&lt;/h3&gt;
&lt;p&gt;If Metro reports an error while loading its configuration, clearing the cache or reinstalling dependencies might not help.&lt;/p&gt;
&lt;p&gt;Look for changes to files such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;metro.config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;babel.config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Expo app configuration, such as &lt;code&gt;app.json&lt;/code&gt; or &lt;code&gt;app.config.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pay particular attention to recent changes involving custom Metro configuration, Babel plugins, aliases, monorepo settings, or package versions.&lt;/p&gt;
&lt;p&gt;For example, if Metro fails while loading &lt;code&gt;metro.config.js&lt;/code&gt;, inspect the configuration and the package named in the error. A missing import or incompatible configuration dependency can prevent the server from starting at all.&lt;/p&gt;
&lt;p&gt;If the problem appeared after a configuration change, compare that change with the last working version. Reverting one recent change is often more informative than rebuilding the entire environment.&lt;/p&gt;
&lt;p&gt;For Expo projects, also make sure you're following the configuration requirements for your installed Expo SDK rather than copying a Metro configuration from an unrelated project or older tutorial.&lt;/p&gt;
&lt;h3 id=&quot;7-if-metro-runs-but-the-app-cannot-connect&quot;&gt;7. If Metro Runs but the App Cannot Connect&lt;/h3&gt;
&lt;p&gt;A running Metro server doesn't guarantee that your app can reach it.&lt;/p&gt;
&lt;p&gt;This distinction matters because repeatedly resetting Metro's cache won't fix a connection problem.&lt;/p&gt;
&lt;h4 id=&quot;check-the-development-server-url&quot;&gt;Check the development-server URL&lt;/h4&gt;
&lt;p&gt;When Metro starts, inspect the URL or connection information it prints in the terminal.&lt;/p&gt;
&lt;p&gt;If you're using a physical device, make sure the device and computer can communicate over the selected network. VPNs, firewalls, guest Wi-Fi networks, and network isolation can interfere with local development connections.&lt;/p&gt;
&lt;h4 id=&quot;if-you-re-using-expo-1&quot;&gt;If you're using Expo&lt;/h4&gt;
&lt;p&gt;Expo supports different connection methods depending on your environment and configuration. If the normal connection method doesn't work, try the tunnel option:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start --tunnel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A tunnel can help when the device cannot reach your computer directly over the local network, although it may be slower and depends on the tunnel service being available.&lt;/p&gt;
&lt;h4 id=&quot;if-you-re-using-an-android-emulator&quot;&gt;If you're using an Android emulator&lt;/h4&gt;
&lt;p&gt;Check that the emulator is running and that the development server is reachable. For a React Native CLI workflow using the standard Android emulator setup, you can forward port &lt;code&gt;8081&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;adb reverse tcp:8081 tcp:8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is useful when the emulator needs a route to Metro through Android Debug Bridge. It isn't a universal fix for every device or connection setup.&lt;/p&gt;
&lt;p&gt;If the app can connect but crashes after loading the bundle, move on to the JavaScript or native error shown by the app. At that point, Metro itself might be working correctly.&lt;/p&gt;
&lt;h3 id=&quot;8-don-t-confuse-a-javascript-error-with-a-metro-startup-failure&quot;&gt;8. Don't Confuse a JavaScript Error With a Metro Startup Failure&lt;/h3&gt;
&lt;p&gt;Sometimes developers blame Metro because the application stops working immediately after a successful bundle.&lt;/p&gt;
&lt;p&gt;But these messages describe different problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metro startup error:&lt;/strong&gt; The development server cannot initialize.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Module resolution error:&lt;/strong&gt; Metro cannot find or resolve a requested module.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript runtime exception:&lt;/strong&gt; The bundle loads, but the application encounters an error while executing code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native build error:&lt;/strong&gt; The Android or iOS application fails during native compilation or configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, if Metro reports that it cannot resolve a module imported by &lt;code&gt;App.tsx&lt;/code&gt;, investigate the import path, filename, package installation, and any configured aliases.&lt;/p&gt;
&lt;p&gt;If the Android build fails before the application starts, inspect the native build output instead of treating every failure as a Metro cache problem.&lt;/p&gt;
&lt;p&gt;Finding the correct failure point saves time because each category requires a different solution.&lt;/p&gt;
&lt;h3 id=&quot;9-when-none-of-these-fixes-works&quot;&gt;9. When None of These Fixes Works&lt;/h3&gt;
&lt;p&gt;If Metro still refuses to start, collect the evidence before making more changes.&lt;/p&gt;
&lt;p&gt;A useful troubleshooting report should include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The exact command used to start Metro.&lt;/li&gt;
&lt;li&gt;The full terminal error, especially the first meaningful error before the stack trace.&lt;/li&gt;
&lt;li&gt;Your operating system.&lt;/li&gt;
&lt;li&gt;Whether the project uses Expo or React Native CLI.&lt;/li&gt;
&lt;li&gt;The relevant React Native and Expo versions.&lt;/li&gt;
&lt;li&gt;Any recent dependency or configuration changes.&lt;/li&gt;
&lt;li&gt;Whether Metro fails to start, gets stuck bundling, or starts while the app cannot connect.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remove access tokens, passwords, private URLs, and other sensitive information before sharing logs publicly.&lt;/p&gt;
&lt;p&gt;If the output contains several errors, don't assume the final message is the root cause. A failure to load one configuration dependency can trigger multiple follow-up errors.&lt;/p&gt;
&lt;p&gt;This is also where a diagnostic tool can help. If you've already tried the obvious fixes and the logs are difficult to interpret, you can paste the relevant error and technical context into &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt;. It can help analyze the failure, identify likely causes, and suggest targeted next steps instead of making you cycle through unrelated fixes.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;When Metro Bundler gets stuck or refuses to start, the best approach is to identify where the failure occurs before changing your project.&lt;/p&gt;
&lt;p&gt;Start with a normal restart, check port &lt;code&gt;8081&lt;/code&gt;, clear the cache when appropriate, and inspect dependency or configuration errors when they appear. If Metro is running but the app cannot connect, troubleshoot the connection rather than repeatedly resetting the bundler.&lt;/p&gt;
&lt;p&gt;The goal isn't to run every possible fix. It's to find the specific reason your development environment stopped working and make the smallest change that resolves it.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/32069/how-to-fix-metro-bundler-stuck-or-not-starting-errors-in-react-native</guid>
<pubDate>Thu, 22 Oct 2026 12:02:08 +0000</pubDate>
</item>
<item>
<title>What part of debugging does AI still struggle with most?</title>
<link>https://coderlegion.com/31582/what-part-of-debugging-does-ai-still-struggle-with-most</link>
<description>&lt;p&gt;AI can find patterns in logs faster.&lt;/p&gt;
&lt;p&gt;The harder part is deciding which pattern actually matters.&lt;/p&gt;
&lt;p&gt;Good debugging still needs context, judgment, and a clear question.&lt;/p&gt;
&lt;p&gt;What part of debugging do you think AI still struggles with most?&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/31582/what-part-of-debugging-does-ai-still-struggle-with-most</guid>
<pubDate>Sun, 18 Oct 2026 05:56:14 +0000</pubDate>
</item>
<item>
<title>Good debugging starts with better questions</title>
<link>https://coderlegion.com/31271/good-debugging-starts-with-better-questions</link>
<description>&lt;p&gt;When production breaks, the first question should not be: &lt;strong&gt;Who broke it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A better starting point is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;What changed in the system?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That shifts the discussion toward evidence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;recent deployments&lt;/li&gt;
&lt;li&gt;configuration changes&lt;/li&gt;
&lt;li&gt;dependency behavior&lt;/li&gt;
&lt;li&gt;traffic patterns&lt;/li&gt;
&lt;li&gt;logs, metrics, and traces&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For me, evidence-first debugging usually leads to faster diagnosis and better team behavior.&lt;/p&gt;
&lt;p&gt;What question do you ask first when a production issue starts?&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/31271/good-debugging-starts-with-better-questions</guid>
<pubDate>Fri, 16 Oct 2026 03:27:21 +0000</pubDate>
</item>
<item>
<title>Why Does Code Work Locally but Fail in Production?</title>
<link>https://coderlegion.com/31069/why-does-code-work-locally-but-fail-in-production</link>
<description>&lt;p&gt;One of the most frustrating problems in software development is when everything works perfectly on your machine, but breaks after deployment. 💀&lt;/p&gt;
&lt;p&gt;The tricky part? The code itself isn't always the problem.&lt;/p&gt;
&lt;p&gt;Here are five things I would investigate first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Environment variables&lt;br&gt;
A missing API key, incorrect URL, or improperly configured environment variable can break an otherwise working application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dependency differences&lt;br&gt;
Different package versions or runtime environments can produce unexpected behavior.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API and CORS configuration&lt;br&gt;
An endpoint that works locally might fail in production because of origin restrictions, incorrect URLs, or deployment configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database permissions&lt;br&gt;
A feature may work with local development permissions but fail when production security policies are enforced.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build and deployment settings&lt;br&gt;
Incorrect routes, missing assets, case-sensitive file paths, and build configuration differences can cause issues that never appeared locally.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My rule is simple: don't assume the deployment is broken until you've identified where the failure actually occurs.&lt;/p&gt;
&lt;p&gt;Reproduce the issue, inspect the logs, isolate the failing layer, and test the fix before deploying again.&lt;/p&gt;
&lt;p&gt;💬 Question for developers: What's the most confusing production bug you've encountered, and what turned out to be the actual cause?&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/31069/why-does-code-work-locally-but-fail-in-production</guid>
<pubDate>Wed, 14 Oct 2026 12:39:53 +0000</pubDate>
</item>
<item>
<title>Bleeding-Edge Python on Windows: Overcoming Day-One Setup Hurdles with UV, VS Code, and Git Bash &amp; G</title>
<link>https://coderlegion.com/30650/bleeding-edge-python-on-windows-overcoming-day-one-setup-hurdles-with-uv-code-and-git-bash</link>
<description>&lt;h2 id=&quot;bleeding-edge-python-on-windows-overcoming-day-one-setup-hurdles-with-uv-vs-code-and-git-bash-amp-gemini-ai&quot;&gt;Bleeding-Edge Python on Windows: Overcoming Day-One Setup Hurdles with UV, VS Code, and Git Bash &amp;amp; Gemini AI&lt;/h2&gt;
&lt;p&gt;Diving into a brand-new major release of a programming language is always exciting, but it comes with a unique set of pioneer challenges. Recently, a few hours ago I embarked on setting up a professional Python development environment on Windows utilizing &lt;strong&gt;Git Bash&lt;/strong&gt;, &lt;strong&gt;VS Code&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/strong&gt;, the &lt;strong&gt;GitHub CLI (&lt;code&gt;gh&lt;/code&gt;)&lt;/strong&gt;, and modern Python libraries like &lt;strong&gt;Typer&lt;/strong&gt;, &lt;strong&gt;Rich&lt;/strong&gt;, and &lt;strong&gt;GitPython&lt;/strong&gt; to build automated command-line tools.&lt;/p&gt;
&lt;p&gt;Along the way, I ran into several classic developer roadblocks—from missing binary wheels to environment path mismatches and authentication overrides. Here is a look at the challenges I faced, why they happened, and how I successfully conquered them, with the assistance of Gemini AI. I simply copied &amp;amp; pasted the error messages I got from the terminal into the chat with Gemini AI&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-bleeding-edge-trap-python-3-15-and-package-compatibility&quot;&gt;1. The Bleeding-Edge Trap: Python 3.15 and Package Compatibility&lt;/h3&gt;
&lt;h4 id=&quot;the-challenge&quot;&gt;The Challenge&lt;/h4&gt;
&lt;p&gt;With the very recent release of &lt;strong&gt;Python 3.15.0&lt;/strong&gt;, developers stepping onto this version gain access to the latest core improvements. However, a brand-new release creates an immediate ecosystem lag: third-party package maintainers have not yet uploaded pre-compiled binary wheels (&lt;code&gt;cp315&lt;/code&gt;) to PyPI for every library.&lt;/p&gt;
&lt;p&gt;When attempting to install dependencies like &lt;strong&gt;PyYAML&lt;/strong&gt;, &lt;code&gt;pip&lt;/code&gt; fell back to building the package from source. Because My Windows lacked the heavy C++ compiler toolchain required to compile those C extensions, the installation failed with a frustrating subprocess error.&lt;/p&gt;
&lt;h4 id=&quot;the-solution&quot;&gt;The Solution&lt;/h4&gt;
&lt;p&gt;I unpacked how Windows handles Python versions—realizing that installing a new version installs it &lt;strong&gt;side-by-side&lt;/strong&gt; rather than overwriting older ones. To bypass compilation nightmares without manually downloading Visual Studio C++ Build Tools, I leveraged &lt;strong&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/strong&gt;, a lightning-fast Python package and project manager, to streamline execution and environment control.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-navigating-shell-environments-and-virtual-environment-paths&quot;&gt;2. Navigating Shell Environments and Virtual Environment Paths&lt;/h3&gt;
&lt;h4 id=&quot;the-challenge-1&quot;&gt;The Challenge&lt;/h4&gt;
&lt;p&gt;Moving between different shells (PowerShell vs. Git Bash) and workspace directories introduced a series of path and execution hiccups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Command Not Found:&lt;/strong&gt; Installing &lt;code&gt;uv&lt;/code&gt; via one shell method left it missing from Git Bash's system &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;.venv&lt;/code&gt; Trap:&lt;/strong&gt; VS Code occasionally saved working script files (&lt;code&gt;scaffold.py&lt;/code&gt; and &lt;code&gt;inspector.py&lt;/code&gt;) directly inside the hidden &lt;code&gt;.venv/Scripts/&lt;/code&gt; folder instead of the root project workspace.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment Mismatches:&lt;/strong&gt; Running standard &lt;code&gt;python&lt;/code&gt; triggered global execution rather than the virtual environment, leading to &lt;code&gt;ModuleNotFoundError&lt;/code&gt; for packages like &lt;code&gt;Typer&lt;/code&gt; despite them being installed elsewhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-solution-1&quot;&gt;The Solution&lt;/h4&gt;
&lt;p&gt;I ironed out a reliable workflow by treating Git Bash and virtual environments with precision:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Module-Level Execution:&lt;/strong&gt; When global paths weren't exposed, I invoked &lt;code&gt;uv&lt;/code&gt; directly through the active Python binary (&lt;code&gt;python3.15 -m uv&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit Targeting:&lt;/strong&gt; I locked down package installations directly to the virtual environment using &lt;code&gt;--python .venv/Scripts/python.exe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workspace Hygiene:&lt;/strong&gt; I located misplaced scripts using file search commands and realigned them to the root &lt;code&gt;~/WebDevelopment/Python/&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-the-github-cli-authentication-override&quot;&gt;3. The GitHub CLI Authentication Override&lt;/h3&gt;
&lt;h4 id=&quot;the-challenge-2&quot;&gt;The Challenge&lt;/h4&gt;
&lt;p&gt;With the project scaffolding script (&lt;code&gt;scaffold.py&lt;/code&gt;) written to automatically initialize Git repositories and push them live using the GitHub CLI (&lt;code&gt;gh&lt;/code&gt;), running a status check revealed an unexpected roadblock:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;X Failed to log in to github.com using token (GITHUB_TOKEN)
- The token in GITHUB_TOKEN is invalid.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though my Github account (&lt;code&gt;QMS85&lt;/code&gt;) was properly saved in the secure system keyring, a stale or rogue &lt;strong&gt;&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; environment variable&lt;/strong&gt; was lingering in the active terminal session, overriding the keyring lookup.&lt;/p&gt;
&lt;h4 id=&quot;the-solution-2&quot;&gt;The Solution&lt;/h4&gt;
&lt;p&gt;The fix was wonderfully simple once identified: I had to clear the conflicting shell variable with a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;unset GITHUB_TOKEN

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Immediately following this, &lt;code&gt;gh auth status&lt;/code&gt; successfully recognized the secure keyring credentials, giving our custom automation scripts full, frictionless access to create public repositories and push code straight to GitHub.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;summary-a-production-ready-workflow&quot;&gt;Summary: A Production-Ready Workflow&lt;/h3&gt;
&lt;p&gt;By diagnosing these hurdles methodically, we turned potential frustrations into a robust, high-performance engineering environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast Dependency Management:&lt;/strong&gt; Powered by &lt;code&gt;uv&lt;/code&gt; for lightning-quick package handling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern CLI Engineering:&lt;/strong&gt; Leveraging &lt;strong&gt;Typer&lt;/strong&gt; for command-line parsing, &lt;strong&gt;Rich&lt;/strong&gt; for gorgeous terminal UI formatting, and &lt;strong&gt;GitPython&lt;/strong&gt; for repository automation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Seamless Version Control:&lt;/strong&gt; Integrated local Git tracking with automated remote publishing via the GitHub CLI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whether you are building internal developer tools or public utilities, setting up a solid foundation ensures that when you code on the bleeding edge, you stay completely in control &amp;amp; You can make use of A.I if you get stuck or need sme debugging assistance....&lt;/p&gt;
&lt;p&gt;Have you installed the latest version of Python 3.15??&lt;br&gt;
What challenges have you faced?&lt;br&gt;
Which is your preferred A.I/LLM Tools To Use ???&lt;br&gt;
Have used An AI/LLM to help you with debugging, whilst busy in the terminal???&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30650/bleeding-edge-python-on-windows-overcoming-day-one-setup-hurdles-with-uv-code-and-git-bash</guid>
<pubDate>Sat, 10 Oct 2026 22:36:18 +0000</pubDate>
</item>
<item>
<title># Debugging ZORGAX: We Captured the Tool-Calling Loop Between Open WebUI and Ollama</title>
<link>https://coderlegion.com/30530/debugging-zorgax-we-captured-the-tool-calling-loop-between-open-webui-and-ollama</link>
<description>&lt;h2 id=&quot;debugging-zorgax-we-captured-the-tool-calling-loop-between-open-webui-and-ollama&quot;&gt;Debugging ZORGAX: We Captured the Tool-Calling Loop Between Open WebUI and Ollama&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;From 28 repeated function calls to a successful end-to-end test — and a new hypothesis about context-window limits.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In my previous article, &lt;strong&gt;Building ZORGAX: Turning a Local LLM into a DevOps AI Agent&lt;/strong&gt;, I described our effort to transform a local language model into a controlled infrastructure assistant for the MyZubster ecosystem.&lt;/p&gt;
&lt;p&gt;ZORGAX runs on Ollama, uses a custom model based on Qwen 2.5 3B, and connects to Open WebUI inside a Docker-based environment.&lt;/p&gt;
&lt;p&gt;Our objective is not to give AI unrestricted control over a server. We want an assistant capable of observing infrastructure, explaining problems, and eventually performing carefully authorized operations.&lt;/p&gt;
&lt;p&gt;But we encountered a problem that made one thing clear:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An AI agent that knows how to call a tool must also know when to stop.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When ZORGAX called &lt;code&gt;list_automations&lt;/code&gt;, the tool correctly returned an empty list. Yet the agent repeatedly called the same function instead of giving a final answer.&lt;/p&gt;
&lt;p&gt;In earlier tests, we observed 28 repeated calls, followed by another run with 16 calls.&lt;/p&gt;
&lt;p&gt;This article documents how we investigated that behavior, what we verified, and why the root cause is still an open research question.&lt;/p&gt;
&lt;h3 id=&quot;1-the-original-problem&quot;&gt;1. The original problem&lt;/h3&gt;
&lt;p&gt;We asked ZORGAX to check active automations.&lt;/p&gt;
&lt;p&gt;The tool returned:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;automations&quot;: [],
  &quot;total&quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The expected answer was straightforward:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;There are currently no active automations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead, ZORGAX kept invoking &lt;code&gt;list_automations&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For a read-only function, unnecessary repetition wastes time and computing resources.&lt;/p&gt;
&lt;p&gt;For a future DevOps agent with permission to restart services, deploy software, or modify infrastructure, uncontrolled repetition could be much more serious.&lt;/p&gt;
&lt;p&gt;We therefore decided to investigate the entire tool-calling workflow before enabling additional operational capabilities.&lt;/p&gt;
&lt;h3 id=&quot;2-testing-zorgax-directly-through-ollama&quot;&gt;2. Testing ZORGAX directly through Ollama&lt;/h3&gt;
&lt;p&gt;Our first experiment bypassed Open WebUI.&lt;/p&gt;
&lt;p&gt;We constructed a synthetic tool-calling conversation using Ollama's &lt;code&gt;/api/chat&lt;/code&gt; endpoint.&lt;/p&gt;
&lt;p&gt;The sequence contained a user request, an assistant function call, and a matching tool response containing the empty automation list.&lt;/p&gt;
&lt;p&gt;ZORGAX completed the interaction successfully.&lt;/p&gt;
&lt;p&gt;It generated a final answer and did not request another tool call.&lt;/p&gt;
&lt;p&gt;This showed that the model &lt;strong&gt;could&lt;/strong&gt; handle an empty tool response correctly.&lt;/p&gt;
&lt;p&gt;However, the direct experiment used a simplified context. It was not identical to the original Open WebUI interaction, so it could not establish which component was responsible for the loop.&lt;/p&gt;
&lt;h3 id=&quot;3-a-suggestion-from-the-developer-community&quot;&gt;3. A suggestion from the developer community&lt;/h3&gt;
&lt;p&gt;After publishing the first article on Coder Legion, a commenter, &lt;strong&gt;lisarconnect&lt;/strong&gt;, suggested capturing the actual request sent to Ollama immediately after the first tool response.&lt;/p&gt;
&lt;p&gt;The key question was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does the next request preserve the assistant's tool call and the corresponding tool result?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This gave us a concrete investigation strategy.&lt;/p&gt;
&lt;p&gt;Instead of guessing whether the problem came from Qwen, Ollama, or Open WebUI, we could inspect the integration boundary directly.&lt;/p&gt;
&lt;p&gt;We also wanted to distinguish a successful empty result from a tool execution error.&lt;/p&gt;
&lt;p&gt;Those two situations should not trigger the same behavior.&lt;/p&gt;
&lt;h3 id=&quot;4-inspecting-open-webui-s-tool-call-handling&quot;&gt;4. Inspecting Open WebUI's tool-call handling&lt;/h3&gt;
&lt;p&gt;We examined the installed Open WebUI source and identified the code responsible for continuing conversations after tool execution.&lt;/p&gt;
&lt;p&gt;We then tested its actual &lt;code&gt;convert_output_to_messages()&lt;/code&gt; function using a synthetic fixture.&lt;/p&gt;
&lt;p&gt;The conversion preserved the expected structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &quot;role&quot;: &quot;assistant&quot;,
    &quot;content&quot;: &quot;&quot;,
    &quot;tool_calls&quot;: [
      {
        &quot;id&quot;: &quot;zorgax-test-001&quot;,
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
          &quot;name&quot;: &quot;list_automations&quot;,
          &quot;arguments&quot;: &quot;{}&quot;
        }
      }
    ]
  },
  {
    &quot;role&quot;: &quot;tool&quot;,
    &quot;tool_call_id&quot;: &quot;zorgax-test-001&quot;,
    &quot;content&quot;: &quot;{\&quot;automations\&quot;:[],\&quot;total\&quot;:0}&quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The assistant tool-call identifier matched the tool-result identifier.&lt;/p&gt;
&lt;p&gt;The empty result was preserved.&lt;/p&gt;
&lt;p&gt;This was an important finding, but it covered only the isolated message-conversion function.&lt;/p&gt;
&lt;p&gt;We still needed evidence from the actual HTTP requests reaching Ollama.&lt;/p&gt;
&lt;h3 id=&quot;5-building-a-separate-diagnostic-environment&quot;&gt;5. Building a separate diagnostic environment&lt;/h3&gt;
&lt;p&gt;To avoid affecting the main MyZubster installation, we deployed a separate Open WebUI instance.&lt;/p&gt;
&lt;p&gt;We also configured:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;CHAT_RESPONSE_MAX_TOOL_CALL_ITERATIONS: &quot;3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the Open WebUI build we inspected, the default tool-call iteration limit was 256.&lt;/p&gt;
&lt;p&gt;Reducing that limit helped prevent another long-running diagnostic loop.&lt;/p&gt;
&lt;p&gt;It was a safety measure, not a fix for the underlying behavior.&lt;/p&gt;
&lt;p&gt;We kept operational tools disabled and used a harmless simulated automation function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Tools:
    def list_automations(self) -&amp;gt; dict:
        &quot;&quot;&quot;Return a synthetic empty automation list.&quot;&quot;&quot;
        return {
            &quot;automations&quot;: [],
            &quot;total&quot;: 0
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function performs no infrastructure operation. It always returns the same predictable result.&lt;/p&gt;
&lt;h3 id=&quot;6-capturing-the-real-open-webui-ollama-requests&quot;&gt;6. Capturing the real Open WebUI → Ollama requests&lt;/h3&gt;
&lt;p&gt;We created a temporary HTTP proxy between the diagnostic Open WebUI instance and Ollama.&lt;/p&gt;
&lt;p&gt;The proxy forwarded requests while recording structural information such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model name&lt;/li&gt;
&lt;li&gt;Available tool names&lt;/li&gt;
&lt;li&gt;Message roles&lt;/li&gt;
&lt;li&gt;Tool-call identifiers&lt;/li&gt;
&lt;li&gt;Presence of the simulated empty result&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We intentionally avoided logging complete private prompts and credentials.&lt;/p&gt;
&lt;p&gt;One earlier captured request advertised approximately &lt;strong&gt;35 built-in tools&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That was very different from our successful direct Ollama test, which exposed only &lt;code&gt;list_automations&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We also reproduced the repeated-call behavior in the isolated Open WebUI environment, where it stopped after reaching the configured three-iteration limit.&lt;/p&gt;
&lt;p&gt;At this stage, the number of tools became an interesting variable to investigate.&lt;/p&gt;
&lt;p&gt;But correlation is not causation.&lt;/p&gt;
&lt;p&gt;We had not yet demonstrated that having many available tools caused the repeated calls.&lt;/p&gt;
&lt;h3 id=&quot;7-the-successful-end-to-end-experiment&quot;&gt;7. The successful end-to-end experiment&lt;/h3&gt;
&lt;p&gt;We created a diagnostic model configuration called &lt;strong&gt;ZORGAX Diagnostic&lt;/strong&gt; and enabled only one harmless tool.&lt;/p&gt;
&lt;p&gt;Then we asked:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Controlla quali automazioni sono attive usando &lt;code&gt;list_automations&lt;/code&gt;. Se non ce ne sono, dimmelo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This time, ZORGAX called the tool once and responded correctly:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Non ci sono automazioni attive.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The proxy captured the complete follow-up structure.&lt;/p&gt;
&lt;p&gt;The first request exposed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tools: ['list_automations']
Messages: ['user']
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next request contained:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tools: ['list_automations']
Messages: ['user', 'assistant', 'tool']
Empty automation result: True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was the evidence we had been looking for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open WebUI successfully transmitted the assistant tool call and the empty tool result to Ollama, and ZORGAX produced a final answer.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We later repeated the successful single-tool experiment.&lt;/p&gt;
&lt;p&gt;The proxy again confirmed that the tool result reached Ollama and that ZORGAX completed the task.&lt;/p&gt;
&lt;p&gt;This does not solve the original loop, but it substantially narrows our investigation.&lt;/p&gt;
&lt;h3 id=&quot;8-a-second-hypothesis-context-window-truncation&quot;&gt;8. A second hypothesis: context-window truncation&lt;/h3&gt;
&lt;p&gt;Another Coder Legion commenter, &lt;strong&gt;Mike Dabydeen&lt;/strong&gt;, suggested examining the model's available context window.&lt;/p&gt;
&lt;p&gt;His hypothesis was that a conversation containing a system prompt, extensive tool definitions, and chat history might exceed the available context.&lt;/p&gt;
&lt;p&gt;If older messages are truncated, the model might receive the latest tool result without sufficient context to understand the original request.&lt;/p&gt;
&lt;p&gt;That could potentially explain repeated tool calls.&lt;/p&gt;
&lt;p&gt;We investigated the Ollama configuration.&lt;/p&gt;
&lt;p&gt;Our environment uses:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Ollama version: 0.32.14
Model: zorgax:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ZORGAX Modelfile does not explicitly specify &lt;code&gt;num_ctx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Ollama service also does not explicitly configure &lt;code&gt;OLLAMA_CONTEXT_LENGTH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After running a diagnostic inference, we queried the loaded model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;NAME             PROCESSOR    CONTEXT
zorgax:latest    100% CPU     4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;We confirmed that the successful single-tool test operated with a 4096-token context window.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We also updated our diagnostic proxy to record explicitly supplied values of &lt;code&gt;num_ctx&lt;/code&gt; and &lt;code&gt;num_predict&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The logging worked correctly.&lt;/p&gt;
&lt;p&gt;For real requests generated by our diagnostic Open WebUI configuration, &lt;code&gt;num_ctx&lt;/code&gt; was not explicitly present in the captured request options.&lt;/p&gt;
&lt;p&gt;That is different from saying the model had no context limit. Ollama still allocated a context window, as confirmed by &lt;code&gt;ollama ps&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;what-remains-unverified&quot;&gt;What remains unverified&lt;/h4&gt;
&lt;p&gt;We have &lt;strong&gt;not yet captured evidence that Ollama actually truncated messages during the problematic 35-tool interaction&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We have also not completed a controlled comparison of the same tool workload at 4096 versus 8192 tokens.&lt;/p&gt;
&lt;p&gt;Context truncation is therefore a plausible hypothesis, not our final diagnosis.&lt;/p&gt;
&lt;p&gt;The next experiment must keep the model, system instructions, tool schemas, and user request as consistent as possible while changing one variable at a time.&lt;/p&gt;
&lt;h3 id=&quot;9-what-these-experiments-taught-us&quot;&gt;9. What these experiments taught us&lt;/h3&gt;
&lt;p&gt;The investigation revealed three separate questions that matter for reliable AI agents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First: Did the tool execute correctly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An empty list can be a successful result. It must not automatically be treated as an error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second: Did the result reach the language model?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The conversation should preserve the relationship between the assistant's tool call and the tool's response.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third: Did the model use the result correctly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even a correctly transmitted response does not guarantee that a model will stop calling tools.&lt;/p&gt;
&lt;p&gt;All three stages need independent testing.&lt;/p&gt;
&lt;p&gt;For infrastructure agents, safeguards must also exist outside the model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Restricted tool permissions&lt;/li&gt;
&lt;li&gt;Bounded execution and retries&lt;/li&gt;
&lt;li&gt;Duplicate-call detection&lt;/li&gt;
&lt;li&gt;Idempotent operations where applicable&lt;/li&gt;
&lt;li&gt;Human authorization for consequential actions&lt;/li&gt;
&lt;li&gt;Auditable execution results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A prompt instruction saying “do not repeat the tool call” is not a substitute for these controls.&lt;/p&gt;
&lt;h3 id=&quot;10-opening-the-investigation-to-contributors&quot;&gt;10. Opening the investigation to contributors&lt;/h3&gt;
&lt;p&gt;We have published our diagnostic findings in the public MyZubster repository.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub investigation issue:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/issues/1577&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/issues/1577&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical reproduction guide:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster/blob/main/docs/zorgax/TOOL-CALLING-INVESTIGATION-2026-10-09.md&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster/blob/main/docs/zorgax/TOOL-CALLING-INVESTIGATION-2026-10-09.md&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Developers working with Ollama, Open WebUI, Qwen, Python, or local AI agents are invited to reproduce the experiments.&lt;/p&gt;
&lt;p&gt;Useful contributions include controlled context-window comparisons, tool-schema experiments, sanitized HTTP request analysis, and regression tests for empty results and repeated calls.&lt;/p&gt;
&lt;p&gt;Contributors can use their own systems. No access to the MyZubster production VPS or private infrastructure is required.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We started with an AI agent repeatedly invoking a function that had already returned a valid result.&lt;/p&gt;
&lt;p&gt;We now have a working diagnostic environment, a trace of the actual tool-result continuation, and two successful end-to-end single-tool experiments.&lt;/p&gt;
&lt;p&gt;We have also confirmed a 4096-token context allocation and identified a new hypothesis worth testing.&lt;/p&gt;
&lt;p&gt;The root cause of the original loop remains open.&lt;/p&gt;
&lt;p&gt;That is an important distinction.&lt;/p&gt;
&lt;p&gt;We are not claiming that we have fixed a bug simply because one controlled configuration works.&lt;/p&gt;
&lt;p&gt;We are building an evidence-based process to understand why the integration behaves differently under different conditions.&lt;/p&gt;
&lt;p&gt;For ZORGAX, the objective remains unchanged: create a useful local DevOps assistant that operates within clear security boundaries and earns trust through reproducible tests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next milestone is a controlled 4096-versus-8192 context experiment with safe tool fixtures.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thanks to &lt;strong&gt;lisarconnect&lt;/strong&gt; and &lt;strong&gt;Mike Dabydeen&lt;/strong&gt; for their technical suggestions and to everyone following the MyZubster development process.&lt;/p&gt;
&lt;p&gt;The investigation continues in public.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; MyZubster Ecosystem&lt;br&gt;
&lt;strong&gt;Agent:&lt;/strong&gt; ZORGAX&lt;br&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Ollama, Qwen 2.5 3B, Open WebUI, Docker, Python, Ubuntu&lt;br&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Experimental; root cause under investigation&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Build in public. Test with evidence. Keep humans in control.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30530/debugging-zorgax-we-captured-the-tool-calling-loop-between-open-webui-and-ollama</guid>
<pubDate>Sat, 10 Oct 2026 03:09:08 +0000</pubDate>
</item>
<item>
<title>Debugging scroll anchoring when a prepended feed shifts</title>
<link>https://coderlegion.com/30510/debugging-scroll-anchoring-when-a-prepended-feed-shifts</link>
<description>&lt;p&gt;If a feed prepends new rows while someone is reading older ones, inspect the scroll container before adding JavaScript to adjust its offset. Chromium may already be compensating for the inserted height through scroll anchoring. A transform on the relevant path can suppress that compensation, producing a jump even though the prepend code did not change.&lt;/p&gt;
&lt;h3 id=&quot;reproduce-the-two-states&quot;&gt;Reproduce the two states&lt;/h3&gt;
&lt;p&gt;Save this as &lt;code&gt;scroll-anchor.html&lt;/code&gt; and open it in Chromium. Click &lt;strong&gt;Prepend&lt;/strong&gt; after the list settles. Then open the same file with &lt;code&gt;?suppressed=1&lt;/code&gt; and repeat. The two runs differ only in the transform applied immediately before the insertion.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button id=&quot;prepend&quot;&amp;gt;Prepend&amp;lt;/button&amp;gt;
&amp;lt;div id=&quot;viewport&quot; class=&quot;viewport&quot;&amp;gt;&amp;lt;ol id=&quot;feed&quot;&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;style&amp;gt;
  .viewport { height: 300px; overflow: auto; }
  .viewport.transform-shift { transform: translateY(0); }
  #feed li { min-height: 72px; }
&amp;lt;/style&amp;gt;
&amp;lt;script&amp;gt;
  const viewport = document.querySelector('#viewport');
  const feed = document.querySelector('#feed');
  for (let index = 0; index &amp;lt; 30; index++) {
    const row = document.createElement('li');
    row.textContent = `Message ${index + 1}`;
    feed.append(row);
  }
  feed.children[16].scrollIntoView({ block: 'center' });

  document.querySelector('#prepend').addEventListener('click', () =&amp;gt; {
    const before = viewport.scrollTop;
    if (location.search.includes('suppressed=1')) {
      viewport.classList.add('transform-shift');
    }
    const row = document.createElement('li');
    row.textContent = 'New message';
    feed.prepend(row);
    requestAnimationFrame(() =&amp;gt; {
      console.log({ before, after: viewport.scrollTop });
    });
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With anchoring enabled, the browser should increase &lt;code&gt;scrollTop&lt;/code&gt; to keep the same message near its prior viewport position. With the transform applied, the controlled Chromium fixture used for the original article leaves the offset unchanged, so visible content moves. Compare both the measured offset and the message in view; a single number alone can hide what the reader experienced.&lt;/p&gt;
&lt;h3 id=&quot;decide-whether-to-keep-anchoring&quot;&gt;Decide whether to keep anchoring&lt;/h3&gt;
&lt;p&gt;If the feed should preserve the current reading position, investigate transforms on the scroller and its ancestors before adding manual scroll correction. If movement is intentional, &lt;code&gt;overflow-anchor: none&lt;/code&gt; is the explicit CSS opt-out. Apply it only to the section where that behavior is wanted, then retest insertion and focus behavior. Avoid disabling anchoring across a whole page to hide a local layout problem.&lt;/p&gt;
&lt;p&gt;This example is a small browser fixture, not a recipe for virtualized lists. The recorded independent test ran in Chromium on Linux; it does not establish identical results in other browsers or in a framework's scrolling abstraction. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll_anchoring/Overview&quot;&gt;MDN scroll anchoring overview&lt;/a&gt; describes suppression triggers, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/overflow-anchor&quot;&gt;MDN &lt;code&gt;overflow-anchor&lt;/code&gt; reference&lt;/a&gt; covers the opt-out.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://chriseugenerodriguez.com/blog/why-does-prepending-items-change-scroll-position-unless-you-disable-scroll-anchoring&quot;&gt;Read the full article and view the tested screenshots&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30510/debugging-scroll-anchoring-when-a-prepended-feed-shifts</guid>
<pubDate>Fri, 09 Oct 2026 19:59:45 +0000</pubDate>
</item>
<item>
<title>The bug that survived being recycled: automaticallyAdjustKeyboardInsets and Fabric view recycling on iOS</title>
<link>https://coderlegion.com/30246/that-survived-being-recycled-automaticallyadjustkeyboardinsets-fabric-view-recycling</link>
<description>&lt;p&gt;After logging a meal in &lt;a rel=&quot;nofollow&quot; href=&quot;https://pragathijayaram.com/portfolio/&quot;&gt;Anovi&lt;/a&gt;, the nutrition app I build, the next screen opened normally. The content was there. Then the first scroll ran past the end of it, into empty space that should not have existed, and stopped.&lt;/p&gt;
&lt;p&gt;Dragging in that space did nothing. Not a slow scroll, not a bounce. Nothing moved, because there was nothing under my finger. The content was somewhere above me and there was no way to reach it. The only way out was to kill the app.&lt;/p&gt;
&lt;p&gt;It was not every screen and not every time, which is what kept it alive through earlier rounds of testing. It produced no crash, no error, and no failed request.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;In short.&lt;/strong&gt; On iOS with the New Architecture, a scroll view that opted into keyboard inset handling can leave that behaviour switched on when React Native recycles it. The next screen to inherit that view gets a phantom inset it never asked for. Filed as &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/facebook/react-native/issues/57755&quot;&gt;react-native#57755&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;If you just want to check and fix your own app, that is &lt;a rel=&quot;nofollow&quot; href=&quot;https://pragathijayaram.com/articles/react-native-keyboard-insets-leaking/&quot;&gt;a separate, shorter piece&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-evidence-that-was-missing&quot;&gt;The evidence that was missing&lt;/h3&gt;
&lt;p&gt;Every request in the sequence returned what it should, in the time it should. Nothing failed, nothing timed out, nothing appeared in the logs at the moment things went wrong. The app had not crashed and was not in an error state.&lt;/p&gt;
&lt;p&gt;Healthy logs are usually read as the absence of a finding. Here they were the finding, because they eliminated the entire category I had been searching in. That is what promoted the next detail from background noise to the only lead.&lt;/p&gt;
&lt;p&gt;The next detail was the keyboard. On the broken screens it appeared, briefly, and went away again. On a screen with no text input on it and nothing focused.&lt;/p&gt;
&lt;p&gt;A keyboard reacting where there is nothing to type into is not a rendering bug. Something was still listening.&lt;/p&gt;
&lt;h3 id=&quot;the-screen-that-broke-was-not-the-screen-at-fault&quot;&gt;The screen that broke was not the screen at fault&lt;/h3&gt;
&lt;p&gt;React Native's new architecture recycles native views. Rather than destroying a component's native view and building a fresh one, it resets the existing view and hands it to the next component that needs the same kind. On iOS, that reset happens in a method called &lt;code&gt;prepareForRecycle&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Recycling is only safe if the reset is complete. Anything left behind belongs to whoever inherits the view next.&lt;/p&gt;
&lt;p&gt;A scroll view can opt into having iOS adjust its insets when the keyboard moves, with a prop called &lt;code&gt;automaticallyAdjustKeyboardInsets&lt;/code&gt;. Underneath, that sets a flag on the native view and gates a keyboard observer that lives for as long as the view does. &lt;code&gt;prepareForRecycle&lt;/code&gt; does not clear that flag.&lt;/p&gt;
&lt;p&gt;So the broken screen was innocent. It had no text input, no keyboard props, and nothing to do with any of this. It had simply been handed a view that came out of the pool still holding someone else's settings.&lt;/p&gt;
&lt;h4 id=&quot;where-the-keyboard-came-from&quot;&gt;Where the keyboard came from&lt;/h4&gt;
&lt;p&gt;The view came from the meal logging modal, which does have a scroll view with the prop set and a search field that focuses itself.&lt;/p&gt;
&lt;p&gt;The interesting part is what happened when I closed it. Tapping Done dismissed the keyboard, reset the modal's internal flow back to its search step, and hid the modal, all in the same render batch. Resetting the flow remounted the search field, its &lt;code&gt;autoFocus&lt;/code&gt; fired, and &lt;strong&gt;the app opened a keyboard inside a modal that was already closing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That is the flicker. Not a leftover from typing, but a keyboard my own code summoned into a dying screen. Its scroll view then went into the recycle pool while that keyboard was still up.&lt;/p&gt;
&lt;p&gt;None of that is a bug. A keyboard being up while a screen goes away is ordinary, and the framework is meant to survive it. What it did instead was hand the next screen a view that was still holding the last one's settings.&lt;/p&gt;
&lt;h3 id=&quot;building-something-that-fails-on-demand&quot;&gt;Building something that fails on demand&lt;/h3&gt;
&lt;p&gt;An explanation that only reproduces inside a nutrition app is not much use to anyone, and it is not something a framework maintainer can act on. So the next step was the smallest thing that still fails: a modal with the prop and a focused input that kills itself mid-animation, then a plain scroll view that mounts straight afterwards and reports its own geometry.&lt;/p&gt;
&lt;h4 id=&quot;the-detector-that-cried-wolf&quot;&gt;The detector that cried wolf&lt;/h4&gt;
&lt;p&gt;Getting the measurement right took longer than finding the bug.&lt;/p&gt;
&lt;p&gt;The first version reported failures that were not failures. A scroll view settling after a bounce moves on its own, and sampling at the wrong moment records that jitter as displacement. Several iterations went into separating &quot;this view is still settling&quot; from &quot;this view has been moved by something else&quot;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A detector that reports a bug which is not there is worse than no detector, because it makes the real signal unfalsifiable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Everything downstream of it, including the controls, would have been noise interpreted as evidence.&lt;/p&gt;
&lt;p&gt;Once it was honest, the numbers were unambiguous. A scroll view that never asked for keyboard insets, with the keyboard hidden, reporting a bottom inset of 217.3 points. Its content was 840 points tall in a 234 point window, so the furthest it should ever scroll is 606. It went to 823.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pragathijayaram.com/media/react-native-scrollview-recycling-bug/harness-pool-dirty.png&quot; alt=&quot;The reproducer on React Native 0.86.2 showing a victim scroll view reporting a bottom inset of 217.3 with the keyboard hidden, content 840 and viewport 234, alongside counters for cycles, rogue keyboard events and corrupt readings.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Physical iPhone, iOS 26.5.2, Release build, React Native 0.86.2, New Architecture. The victim sets no keyboard props of any kind.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-controls-are-the-argument&quot;&gt;The controls are the argument&lt;/h3&gt;
&lt;p&gt;A reproducer that fails is a claim. A reproducer that fails, and stops failing when you remove exactly one ingredient, is evidence.&lt;/p&gt;
&lt;p&gt;Two controls, each from a freshly killed process. Turn off the inset prop: nothing. Dismiss the keyboard and let it settle before the modal closes: nothing. Each ingredient is necessary and neither is sufficient. The failure needs a view configured for keyboard insets to be recycled while its observer is still live.&lt;/p&gt;
&lt;h3 id=&quot;measuring-it-instead-of-reasoning-about-it&quot;&gt;Measuring it instead of reasoning about it&lt;/h3&gt;
&lt;p&gt;Everything so far was inference, from reading source and watching behaviour. The mechanism deserved a measurement, so I instrumented the framework itself: every keyboard delivery logged with the view's address and whether it was on screen or in the recycle pool.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;rec-in  view=0x108118c00 ivar=1          entering prepareForRecycle
rec-out view=0x108118c00 ivar=1          leaving it, flag still set
kbd     view=0x108118c00 ivar=1 POOLED
write   view=0x108118c00 bottom=217.3 POOLED    ‚Üê inset written while pooled
props   view=0x108118c00 old=1 new=0 ivar=0     ‚Üê next screen mounts, flag cleared
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The write lands &lt;strong&gt;while the view is sitting in the pool&lt;/strong&gt;, 105ms before anything mounts on it. When a screen does mount, the flag is cleared immediately, so the screen you can see is never written to at all.&lt;/p&gt;
&lt;p&gt;That ruled out the explanation I had been carrying. An earlier test, where a poisoned view's scroll position moved after I focused an unrelated text field, had looked like proof that the recycled view was still listening while mounted. It was not. The keyboard changed the layout, the window grew from 234 points to 299, the furthest legal scroll position dropped by 65, and iOS pulled the offset back to the new limit. Arithmetic, not a live listener.&lt;/p&gt;
&lt;h3 id=&quot;what-is-actually-happening&quot;&gt;What is actually happening&lt;/h3&gt;
&lt;p&gt;The pooled view keeps receiving keyboard notifications and keeps writing insets onto itself. It is not stale; it is current. It tracks the keyboard continuously, taking a real value when a keyboard rises and zero when it falls.&lt;/p&gt;
&lt;p&gt;Whatever value it happens to be holding at the moment someone inherits it is what that screen is stuck with, because the inset is only reassigned when a component's props change it, and a component that never set the prop has nothing to reassign.&lt;/p&gt;
&lt;p&gt;Which means the whole thing turns on timing. Sweeping the delay before the modal tears itself down, ten cycles each from a fresh process:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;modal torn down after &lt;/th&gt;&lt;th&gt;corrupt readings &lt;/th&gt;&lt;th&gt;phantom inset&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;~120ms &lt;/td&gt;&lt;td&gt;36 &lt;/td&gt;&lt;td&gt;217.3&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;~220ms &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;~320ms &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;~520ms &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;keyboard dismissed first &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Only the earliest teardown reproduces. Wait longer and the keyboard's own disappearance reaches the pooled view first, zeroes the inset, and the pool quietly cleans itself. The bug needs a screen to mount during the narrow window while a keyboard is up.&lt;/p&gt;
&lt;p&gt;That is also why it was intermittent in production, and why it survived earlier testing rounds. Most of the time nothing mounts inside that window.&lt;/p&gt;
&lt;h4 id=&quot;why-it-could-not-be-scrolled-back&quot;&gt;Why it could not be scrolled back&lt;/h4&gt;
&lt;p&gt;The phantom inset extends the scrollable range past the end of the content, and that extra region has no view underneath it. So it takes no touches. A drag there produces no scroll events at all.&lt;/p&gt;
&lt;p&gt;In the reproducer a sliver of the last row stays visible, so you can grab that and pull yourself back. In the app the scroll views were full screen. Once the content had left the viewport there was nothing to grab, and no gesture in the app could recover it.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A screen recording of the reproducer on a physical device is on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://pragathijayaram.com/articles/react-native-scrollview-recycling-bug/&quot;&gt;original article&lt;/a&gt;: several poison cycles, a scroll up into the space that should not exist, then taps on an unrelated input.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the difference between a layout glitch and a screen that is finished until you kill the process.&lt;/p&gt;
&lt;h3 id=&quot;what-happened-after-filing&quot;&gt;What happened after filing&lt;/h3&gt;
&lt;p&gt;I reproduced it unchanged on React Native 0.86.2, the latest release at the time, and filed it as &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/facebook/react-native/issues/57755&quot;&gt;react-native#57755&lt;/a&gt; with a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/PragathiJ/rn-aaki-recycle-repro&quot;&gt;public reproducer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Two pull requests followed, from people I have never met. The second carries a regression test covering the whole lifecycle, and a Meta engineer imported it for internal review the same morning it appeared.&lt;/p&gt;
&lt;p&gt;Its two lines are small: clear the flag when the view is recycled, and stop guarding the assignment behind a props comparison so a recycled view can be re-armed properly. I applied them to the reproducer and ran it on the phone that found the bug in the first place. Baseline immediately before: 36 corrupt readings in 10 cycles. With the patch: 49 cycles, zero.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-take-from-this&quot;&gt;What I would take from this&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The healthy logs were the finding.&lt;/strong&gt; They eliminated a whole category, which is what turned a flicker into the only lead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Controls before claims.&lt;/strong&gt; Any bug can be explained by a plausible story. The question is which single ingredient you can remove to make it stop, and whether you have actually tried removing it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A detector that lies is worse than no detector.&lt;/strong&gt; Most of the reproducer work went into eliminating false positives, not into finding the failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Instrument the explanation, not just the bug.&lt;/strong&gt; A story that fits every observation is not the same as a correct one. Mine was in the right place and the wrong moment, and only logging the framework showed the difference.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you think your own app has this, the checks and the three ways to fix it are in &lt;a rel=&quot;nofollow&quot; href=&quot;https://pragathijayaram.com/articles/react-native-keyboard-insets-leaking/&quot;&gt;a shorter companion piece&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The reproducer is at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/PragathiJ/rn-aaki-recycle-repro&quot;&gt;github.com/PragathiJ/rn-aaki-recycle-repro&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;I write about production engineering and applied AI at &lt;a rel=&quot;nofollow&quot; href=&quot;https://pragathijayaram.com/articles/&quot;&gt;pragathijayaram.com&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30246/that-survived-being-recycled-automaticallyadjustkeyboardinsets-fabric-view-recycling</guid>
<pubDate>Thu, 08 Oct 2026 09:12:17 +0000</pubDate>
</item>
<item>
<title>automaticallyAdjustKeyboardInsets is leaking into your other screens: how to check and fix it</title>
<link>https://coderlegion.com/30241/automaticallyadjustkeyboardinsets-is-leaking-into-your-other-screens-how-check-and-fix</link>
<description>&lt;p&gt;A screen opens normally, then scrolls past the end of its content into empty&lt;br&gt;
space, and dragging in that space does nothing at all. Restarting the app is the&lt;br&gt;
only way out.&lt;/p&gt;
&lt;p&gt;If that sounds familiar, this is a React Native framework defect, not your code.&lt;br&gt;
This page is the operational version: how to confirm it, how to find every source&lt;br&gt;
in your project, and what to do. The investigation behind it, including why it&lt;br&gt;
took a while to understand, &lt;a rel=&quot;nofollow&quot; href=&quot;https://pragathijayaram.com/articles/react-native-scrollview-recycling-bug/&quot;&gt;is here&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Scope.&lt;/strong&gt; iOS only, New Architecture only. The code involved is the Fabric iOS&lt;br&gt;
scroll view, so the old architecture and Android take different paths.&lt;br&gt;
Reproduced on React Native 0.81.4 and 0.86.2. Filed as&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/facebook/react-native/issues/57755&quot;&gt;react-native#57755&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;what-is-going-wrong&quot;&gt;What is going wrong&lt;/h3&gt;
&lt;p&gt;A scroll view that sets &lt;code&gt;automaticallyAdjustKeyboardInsets&lt;/code&gt; keeps that behaviour&lt;br&gt;
switched on when React Native recycles its native view. While the view sits in the&lt;br&gt;
recycle pool it carries on receiving keyboard notifications and writing insets&lt;br&gt;
onto itself. The next component to be handed that view inherits whatever value was&lt;br&gt;
there, and nothing puts it back, because a component that never set the prop has&lt;br&gt;
no inset of its own to assign.&lt;/p&gt;
&lt;p&gt;The result is a scroll view with extra scrollable space below its content. That&lt;br&gt;
space has no view underneath it, so it takes no touches, which is why you cannot&lt;br&gt;
drag your way back out of it.&lt;/p&gt;
&lt;h3 id=&quot;confirm-it&quot;&gt;Confirm it&lt;/h3&gt;
&lt;p&gt;The first sign is a keyboard appearing on a screen with nothing focused. That is&lt;br&gt;
the tell, not the bug.&lt;/p&gt;
&lt;p&gt;The test is to read the inset on a scroll view that should not have one. Put this&lt;br&gt;
on a screen that sets no insets of its own, scroll it with the keyboard hidden,&lt;br&gt;
and watch the console:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const reported = React.useRef(false);

&amp;lt;ScrollView
  scrollEventThrottle={16}
  onScroll={(e) =&amp;gt; {
    // contentInset is iOS-only; on Android nativeEvent has no such field.
    const bottom = e.nativeEvent.contentInset?.bottom ?? 0;
    if (bottom &amp;gt; 0 &amp;amp;&amp;amp; !reported.current) {
      reported.current = true;
      console.warn('phantom bottom inset', bottom);
    }
  }}
&amp;gt;
  &amp;lt;View style={{ height: 2000 }} /&amp;gt;
&amp;lt;/ScrollView&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two details decide whether this test is worth anything. The scroll view needs&lt;br&gt;
content taller than itself, or it never scrolls, &lt;code&gt;onScroll&lt;/code&gt; never fires, and you&lt;br&gt;
will conclude you are clean without having tested anything. And the reading has to&lt;br&gt;
be gated, because at a 16ms throttle an ungated &lt;code&gt;console.warn&lt;/code&gt; buries your log.&lt;/p&gt;
&lt;p&gt;A scroll view that never opted in should report zero. Anything else means it is&lt;br&gt;
sitting on a view that came out of the pool still armed.&lt;/p&gt;
&lt;h3 id=&quot;find-every-source&quot;&gt;Find every source&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -rn &quot;automaticallyAdjustKeyboardInsets&quot; src/
grep -rn &quot;contentInset&quot; src/   # also catches contentInsetAdjustmentBehavior
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second search matters more than it looks. Any prop that writes native insets&lt;br&gt;
belongs to the native view, and therefore to the recycling pool, so a static&lt;br&gt;
&lt;code&gt;contentInset&lt;/code&gt; combined with an automatic &lt;code&gt;contentInsetAdjustmentBehavior&lt;/code&gt; is the&lt;br&gt;
same class of risk even though it has nothing to do with keyboards. In my own&lt;br&gt;
sweep that turned up a grid component the first search had missed.&lt;/p&gt;
&lt;p&gt;Then run both again against &lt;code&gt;node_modules&lt;/code&gt;. The pool is per process, not per&lt;br&gt;
module, so a scroll view inside a dependency arms it exactly as well as one of&lt;br&gt;
yours. You can clean your own code completely and still be handed a poisoned view by a library you did not write.&lt;/p&gt;
&lt;h3 id=&quot;fix-it&quot;&gt;Fix it&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Option 1, remove the prop.&lt;/strong&gt; Handle keyboard layout explicitly instead. This is&lt;br&gt;
what I shipped.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// Before: the flag does the layout, and the native view keeps listening
&amp;lt;Modal visible={visible}&amp;gt;
  &amp;lt;ScrollView automaticallyAdjustKeyboardInsets&amp;gt;
    &amp;lt;TextInput autoFocus /&amp;gt;
  &amp;lt;/ScrollView&amp;gt;
&amp;lt;/Modal&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// After: explicit layout, nothing left armed on the native view
&amp;lt;Modal visible={visible}&amp;gt;
  &amp;lt;KeyboardAvoidingView behavior=&quot;padding&quot; style={{ flex: 1 }}&amp;gt;
    &amp;lt;ScrollView contentContainerStyle={{ paddingBottom: 24 }}&amp;gt;
      &amp;lt;TextInput autoFocus={visible} /&amp;gt;
    &amp;lt;/ScrollView&amp;gt;
  &amp;lt;/KeyboardAvoidingView&amp;gt;
&amp;lt;/Modal&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;autoFocus={visible}&lt;/code&gt; change matters more than it looks. Focusing on mount is&lt;br&gt;
what starts a keyboard animation during modal presentation, which is exactly the&lt;br&gt;
window the defect needs. Watch for the same shape anywhere a modal resets its&lt;br&gt;
internal state as it closes: that can remount a focused field and open a keyboard&lt;br&gt;
inside a screen that is already going away.&lt;/p&gt;
&lt;p&gt;One caveat: &lt;code&gt;KeyboardAvoidingView&lt;/code&gt; with &lt;code&gt;behavior=&quot;padding&quot;&lt;/code&gt; inside a &lt;code&gt;Modal&lt;/code&gt;&lt;br&gt;
behaves differently depending on presentation style and often wants a&lt;br&gt;
&lt;code&gt;keyboardVerticalOffset&lt;/code&gt;. Treat it as a shape to adapt, not a drop-in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2, keep the prop but dismiss the keyboard first.&lt;/strong&gt; Dismiss it and let it&lt;br&gt;
settle before the screen unmounts. This came back at zero in the reproducer. It is&lt;br&gt;
narrower than option 1, because it depends on every exit path from that screen&lt;br&gt;
doing the same thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 3, patch the framework locally.&lt;/strong&gt; Until the upstream fix ships you can&lt;br&gt;
carry it as a &lt;code&gt;patch-package&lt;/code&gt; patch. In &lt;code&gt;RCTScrollViewComponentView.mm&lt;/code&gt;, clear the&lt;br&gt;
flag on recycle and assign it on every update instead of only when it changes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-objc&quot;&gt;// prepareForRecycle: disarm before the view goes back in the pool
_automaticallyAdjustKeyboardInsets = NO;

// updateProps: drop the old-vs-new condition around the assignment
_automaticallyAdjustKeyboardInsets = newScrollViewProps.automaticallyAdjustKeyboardInsets;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both lines are needed. The first stops the leak. The second matters because&lt;br&gt;
&lt;code&gt;prepareForRecycle&lt;/code&gt; does not reset the stored props, so once the flag is cleared a&lt;br&gt;
view being reused by another component that &lt;em&gt;also&lt;/em&gt; wants keyboard insets would&lt;br&gt;
skip the assignment and silently lose the behaviour.&lt;/p&gt;
&lt;p&gt;This is the substance of &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/facebook/react-native/pull/57811&quot;&gt;react-native#57811&lt;/a&gt;, which a Meta engineer has imported for internal review. I verified those changes against the reproducer on a physical device: 49 cycles, zero failures, where the unpatched build produced 36 failures in 10 cycles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you are on 0.86.2 and patching the framework&lt;/strong&gt;, note that React core ships&lt;br&gt;
prebuilt, so edits to &lt;code&gt;RCTScrollViewComponentView.mm&lt;/code&gt; in &lt;code&gt;node_modules&lt;/code&gt; never&lt;br&gt;
reach the binary. You need &lt;code&gt;RCT_USE_PREBUILT_RNCORE=0&lt;/code&gt; and a fresh &lt;code&gt;pod install&lt;/code&gt;,&lt;br&gt;
or the patch silently does nothing.&lt;/p&gt;
&lt;h3 id=&quot;what-does-not-work&quot;&gt;What does not work&lt;/h3&gt;
&lt;p&gt;Scrolling the screen back to the top when it opens. I shipped that early and it&lt;br&gt;
does clear the wrong opening position, which is enough to make it look like a&lt;br&gt;
cure. It does not remove the phantom inset, so the dead space is still waiting at&lt;br&gt;
the bottom.&lt;/p&gt;
&lt;h3 id=&quot;how-to-tell-you-have-fixed-it&quot;&gt;How to tell you have fixed it&lt;/h3&gt;
&lt;p&gt;Run the confirmation check again after the flow that used to break things. Zero&lt;br&gt;
inset, and a scroll to the bottom that stops at the end of your content with&lt;br&gt;
nothing beneath it.&lt;/p&gt;
&lt;p&gt;The reproducer is at&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/PragathiJ/rn-aaki-recycle-repro&quot;&gt;github.com/PragathiJ/rn-aaki-recycle-repro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I write about production engineering and applied AI at&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://pragathijayaram.com/articles/&quot;&gt;pragathijayaram.com&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30241/automaticallyadjustkeyboardinsets-is-leaking-into-your-other-screens-how-check-and-fix</guid>
<pubDate>Thu, 08 Oct 2026 08:56:07 +0000</pubDate>
</item>
<item>
<title>Fixing KV Propagation Delay in Media Publishing</title>
<link>https://coderlegion.com/30080/fixing-kv-propagation-delay-in-media-publishing</link>
<description>&lt;p&gt;When you build a serverless media pipeline that stores generated images in a key-value store and immediately sends those public URLs to a third-party publishing API, you will likely encounter a frustrating failure mode. Your local tests pass, your terminal curl requests return the correct image bytes with a 200 OK status, yet the publishing API responds with a cryptic media not found error. This happens because your own worker reads from the exact edge location that just wrote the key, while the platform fetcher hits a different edge node that has not received the replicated data yet. For a related implementation, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/android-viewmodel-partial-success-refresh-failure/&quot;&gt;Viewmodel Partial Success Refresh Failure&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;green-locally-dead-remotely&quot;&gt;Green Locally, Dead Remotely&lt;/h3&gt;
&lt;p&gt;The most deceptive part of this issue is its misleading diagnostic feedback. When publishing fails, the natural instinct is to examine the container ordering, parameter payloads, or authentication tokens. Developers often curl the generated public URL from their own workstation or test runner, receive the correct MIME type and image bytes, and conclude that the serving layer works fine. This self-verification is a trap. Your edge succeeding proves only that one specific node has the data. It provides zero visibility into whether the global key-value fabric has synchronized across all regional datacenters.&lt;/p&gt;
&lt;h3 id=&quot;whose-edge-is-it-anyway&quot;&gt;Whose Edge Is It Anyway&lt;/h3&gt;
&lt;p&gt;Global key-value stores prioritize low-latency writes and eventual consistency. When your worker executes a put operation, the runtime confirms success once the local write succeeds. Replication to other regions happens asynchronously in the background. If a third-party platform attempts to download the asset immediately after receiving the URL, its request routes to an edge server near its own infrastructure. If that specific server has not received the update, the fetch fails. Because distributed platforms do not expose their internal replication state or routing tables, you cannot poll their edge directly to check availability. You must instead build a defensive timing model into your application code.&lt;/p&gt;
&lt;h3 id=&quot;two-waits-two-handoffs&quot;&gt;Two Waits, Two Handoffs&lt;/h3&gt;
&lt;p&gt;Solving this problem requires separating your publishing pipeline into distinct phases, each guarded by its own deliberate delay. Conflating these phases leads to brittle workarounds.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Propagation Wait&lt;/strong&gt;: After storing the asset in key-value storage, introduce a measured pause to allow global replication. This gives remote edge locations time to catch up before any external system attempts a download.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Processing Wait&lt;/strong&gt;: After the third-party platform initiates its import or container creation process, allow an additional window for the platform to process the file before you invoke the final publish command.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hardcoding these delays into your core logic makes your test suite painfully slow. Instead, make your wait durations configurable by injecting milliseconds during testing while using production-appropriate values for live deployments. Keeping these constants clearly named in your source code prevents future maintainers from accidentally optimizing them away.&lt;/p&gt;
&lt;h3 id=&quot;implementing-the-fix&quot;&gt;Implementing the Fix&lt;/h3&gt;
&lt;p&gt;To manage these handoffs safely, structure your asynchronous worker code to wait explicitly between storage and publishing steps. Here is an example configuration pattern in TypeScript that uses injected wait durations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface PublishConfig {
  propagationDelayMs: number;
  processingDelayMs: number;
  maxRetries: number;
}

async function storeAndPublishMedia(
  kv: KVNamespace,
  key: string,
  data: ArrayBuffer,
  config: PublishConfig
): Promise&amp;lt;string&amp;gt; {
  await kv.put(key, data);
  
  // Guard against global edge replication lag
  await new Promise((resolve) =&amp;gt; setTimeout(resolve, config.propagationDelayMs));
  
  const mediaId = await initiatePlatformUpload(key);
  
  // Guard against remote processing lag
  await new Promise((resolve) =&amp;gt; setTimeout(resolve, config.processingDelayMs));
  
  return await finalizePublish(mediaId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;verifying-the-publish-object&quot;&gt;Verifying the Publish Object&lt;/h3&gt;
&lt;p&gt;Never rely on your own curl command as proof of a successful publish operation. True success is defined solely by the response of the downstream publishing API. When the platform successfully fetches the asset from its local edge, processes it, and returns a valid media object with attached children or confirmation IDs, your pipeline has succeeded. If the call fails, adjust your propagation budget upward or inspect the platform error logs for download timeouts.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Eventual consistency models introduce hidden timing assumptions into modern serverless architectures. When your application hands off assets to external consumers, assuming instant global visibility will cause intermittent failures. By acknowledging edge propagation realities, separating your storage waits from your processing waits, and verifying platform objects rather than local URLs, you can make your media publishing pipelines resilient to distributed latency.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30080/fixing-kv-propagation-delay-in-media-publishing</guid>
<pubDate>Wed, 07 Oct 2026 06:02:44 +0000</pubDate>
</item>
<item>
<title>Debugging Salesforce Apex - 8 Gotchas That Waste Everyone's Weekend</title>
<link>https://coderlegion.com/29142/debugging-salesforce-apex-8-gotchas-that-waste-everyones-weekend</link>
<description>&lt;hr&gt;
&lt;p&gt;title: Debugging Salesforce Apex - 8 Gotchas That Waste Everyone's Weekend&lt;br&gt;
published: true&lt;br&gt;
description: 8 Gotchas That Waste Everyone's Weekend&lt;br&gt;
tags: salesforce, apex, debugging, tutorial&lt;/p&gt;
&lt;h2 id=&quot;published-at-2026-07-17-13-16-0000&quot;&gt;published_at: 2026-07-17 13:16 +0000&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;If you've ever stared at a Salesforce debug log wondering why a trigger fired twice, why your query &quot;worked yesterday,&quot; or why a batch job silently ate an exception — this one's for you.&lt;/p&gt;
&lt;p&gt;Apex debugging is different from debugging most languages because you're not just fighting your own code. You're fighting governor limits, an opaque order of execution, and a debug log format that seems designed to hide the one line you need. Here are the gotchas that trip people up most, with real code.&lt;/p&gt;
&lt;h3 id=&quot;1-your-catch-block-is-swallowing-the-actual-error&quot;&gt;1. Your &lt;code&gt;catch&lt;/code&gt; block is swallowing the actual error&lt;/h3&gt;
&lt;p&gt;This is the single most common reason &quot;nothing shows up in the logs&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;try {
    insert accounts;
} catch (Exception e) {
    System.debug('Something went wrong');
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You caught the exception, printed a string with zero context, and moved on. When it breaks in production, you have nothing to go on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;try {
    insert accounts;
} catch (DmlException e) {
    for (Integer i = 0; i &amp;lt; e.getNumDml(); i++) {
        System.debug(LoggingLevel.ERROR,
            'DML failed on row ' + e.getDmlIndex(i) +
            ': ' + e.getDmlMessage(i));
    }
    throw e; // don't swallow it unless you have a real reason to
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Catch specific exception types (&lt;code&gt;DmlException&lt;/code&gt;, &lt;code&gt;QueryException&lt;/code&gt;, &lt;code&gt;NullPointerException&lt;/code&gt;) instead of generic &lt;code&gt;Exception&lt;/code&gt; whenever you can — the specific types give you methods like &lt;code&gt;getDmlMessage()&lt;/code&gt; and &lt;code&gt;getDmlIndex()&lt;/code&gt; that a bare &lt;code&gt;Exception&lt;/code&gt; doesn't.&lt;/p&gt;
&lt;h3 id=&quot;2-debug-logs-get-truncated-at-2mb-and-truncate-from-the-middle&quot;&gt;2. Debug logs get truncated at 2MB — and truncate from the middle&lt;/h3&gt;
&lt;p&gt;Salesforce debug logs have a hard 20MB size cap per transaction, but the piece you actually read in Setup often gets cut around 2MB depending on log level. When a log is huge, Salesforce removes lines from the &lt;em&gt;middle&lt;/em&gt;, not the end — so you'll see the start of your transaction and the finish, with the actual bug missing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Don't set every category to &lt;code&gt;FINEST&lt;/code&gt;. Set only what you need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;APEX_CODE: DEBUG (not FINEST)
DB: INFO
WORKFLOW: INFO
VALIDATION: INFO
CALLOUT: INFO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FINEST&lt;/code&gt; on &lt;code&gt;APEX_CODE&lt;/code&gt; logs every variable assignment and method entry/exit — it's the fastest way to blow past the size limit and lose the exact line you care about.&lt;/p&gt;
&lt;h3 id=&quot;3-governor-limits-don-t-show-up-until-they-blow-up&quot;&gt;3. Governor limits don't show up until they blow up&lt;/h3&gt;
&lt;p&gt;Apex is multi-tenant, so you get hard caps: 100 SOQL queries, 150 DML statements, 10,000 SOQL rows, 10 seconds of synchronous CPU time. The gotcha isn't that limits exist — it's that code that looks fine in isolation fails the moment it runs in bulk (more on that in #4).&lt;/p&gt;
&lt;p&gt;Instrument &lt;em&gt;before&lt;/em&gt; you hit the wall:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;System.debug('SOQL queries used: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
System.debug('DML used: ' + Limits.getDmlStatements() + ' / ' + Limits.getLimitDmlStatements());
System.debug('CPU time: ' + Limits.getCpuTime() + ' / ' + Limits.getLimitCpuTime());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Drop these at the top and bottom of suspect methods. When someone says &quot;it works for one record but times out on a bulk import,&quot; this is how you find out where.&lt;/p&gt;
&lt;h3 id=&quot;4-soql-dml-inside-a-for-loop-the-classic-that-still-gets-everyone&quot;&gt;4. SOQL/DML inside a &lt;code&gt;for&lt;/code&gt; loop — the classic that still gets everyone&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;// DO NOT DO THIS
for (Contact c : triggerNew) {
    Account a = [SELECT Id, Name FROM Account WHERE Id = :c.AccountId]; // query in a loop
    a.Last_Contact_Date__c = Date.today();
    update a; // DML in a loop
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This passes code review constantly because the person testing it only inserted one record. Insert 200 Contacts via a Data Loader batch and you'll blow past the 100-query or 150-DML limit instantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix — bulkify it:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;Set&amp;lt;Id&amp;gt; accountIds = new Set&amp;lt;Id&amp;gt;();
for (Contact c : triggerNew) {
    if (c.AccountId != null) accountIds.add(c.AccountId);
}

Map&amp;lt;Id, Account&amp;gt; accountsToUpdate = new Map&amp;lt;Id, Account&amp;gt;(
    [SELECT Id, Name FROM Account WHERE Id IN :accountIds]
);

for (Contact c : triggerNew) {
    if (accountsToUpdate.containsKey(c.AccountId)) {
        accountsToUpdate.get(c.AccountId).Last_Contact_Date__c = Date.today();
    }
}

update accountsToUpdate.values();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One query, one DML statement, regardless of batch size.&lt;/p&gt;
&lt;h3 id=&quot;5-test-starttest-test-stoptest-reset-limits-this-can-hide-real-bugs&quot;&gt;5. &lt;code&gt;Test.startTest()&lt;/code&gt; / &lt;code&gt;Test.stopTest()&lt;/code&gt; reset limits — this can hide real bugs&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;@isTest
static void testBulkInsert() {
    List&amp;lt;Account&amp;gt; accs = TestDataFactory.createAccounts(200);
    Test.startTest();
    insert accs;
    Test.stopTest();
    // asserts...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Test.startTest()&lt;/code&gt; gives you a &lt;em&gt;fresh&lt;/em&gt; set of governor limits and forces async work (&lt;code&gt;@future&lt;/code&gt;, queueables, batch chunks) to run synchronously inside &lt;code&gt;stopTest()&lt;/code&gt;. That's great for testing async logic — but it also means limit-related bugs that would show up in a single real transaction can hide inside your test because the limits reset partway through. If a bug only appears in production and never in tests, check whether your DML/queries are split across the &lt;code&gt;startTest&lt;/code&gt;/&lt;code&gt;stopTest&lt;/code&gt; boundary in a way production traffic never gets.&lt;/p&gt;
&lt;h3 id=&quot;6-relationship-field-queries-return-null-not-an-exception-until-you-touch-a-field-on-them&quot;&gt;6. Relationship field queries return &lt;code&gt;null&lt;/code&gt;, not an exception — until you touch a field on them&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;Contact con = [SELECT Id, Account.Name, Account.Owner.Email FROM Contact WHERE Id = :conId];
System.debug(con.Account.Name); // fine if Account exists
System.debug(con.Account.Owner.Email); // NullPointerException if Account is null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;AccountId&lt;/code&gt; is null on that Contact, &lt;code&gt;con.Account&lt;/code&gt; itself is &lt;code&gt;null&lt;/code&gt; — and reaching for &lt;code&gt;.Owner&lt;/code&gt; on a null &lt;code&gt;Account&lt;/code&gt; throws a &lt;code&gt;NullPointerException&lt;/code&gt; with a stack trace that just says &quot;line 47,&quot; giving you no hint about &lt;em&gt;which&lt;/em&gt; relationship was null.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Guard each hop, or flatten with a null-safe pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;String ownerEmail = (con.Account != null &amp;amp;&amp;amp; con.Account.Owner != null)
    ? con.Account.Owner.Email
    : null;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;7-without-sharing-vs-with-sharing-causes-silent-missing-data-not-errors&quot;&gt;7. &lt;code&gt;without sharing&lt;/code&gt; vs &lt;code&gt;with sharing&lt;/code&gt; causes silent missing data, not errors&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;public without sharing class DataProcessor {
    public List&amp;lt;Account&amp;gt; getAccounts() {
        return [SELECT Id, Name FROM Account]; // sees ALL records, ignores sharing rules
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The dangerous version of this bug is the reverse: a class declared &lt;code&gt;with sharing&lt;/code&gt; (or with no declaration, which inherits the caller's context) queries fewer records than expected for a particular user, and nothing throws an error — the query just returns 3 rows instead of 300. This is brutal to debug because it looks like a data problem, not a code problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; When you get a &quot;missing records&quot; bug report, check the sharing keyword on every class in the call stack before you touch the query itself. Explicitly declare &lt;code&gt;with sharing&lt;/code&gt; / &lt;code&gt;without sharing&lt;/code&gt; / &lt;code&gt;inherited sharing&lt;/code&gt; on every class — don't rely on the default.&lt;/p&gt;
&lt;h3 id=&quot;8-async-debug-logs-don-t-show-up-where-you-re-looking&quot;&gt;8. Async debug logs don't show up where you're looking&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@future&lt;/code&gt; methods, Queueable jobs, and Batch Apex execute in a &lt;em&gt;separate transaction&lt;/em&gt;, often minutes later, under a different debug log entry than the one you're staring at.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apex&quot;&gt;@future
public static void updateRelatedRecords(Set&amp;lt;Id&amp;gt; recordIds) {
    System.debug('This log will NOT appear in the log of the code that called this method');
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Set up a trace flag on the &lt;em&gt;user&lt;/em&gt;, not just for a single anticipated transaction, and go to &lt;strong&gt;Setup → Debug Logs&lt;/strong&gt; to find the log entry with a later timestamp and an &quot;Application: Async&quot; indicator. For batch jobs, check &lt;strong&gt;Setup → Apex Jobs&lt;/strong&gt; for the Job ID, and consider writing key checkpoints to a custom object or &lt;code&gt;Exception&lt;/code&gt; records so you have a permanent record instead of a log that expires.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-reference&quot;&gt;Quick reference&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Symptom &lt;/th&gt;&lt;th&gt;Likely cause&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Works with 1 record, fails on bulk import &lt;/td&gt;&lt;td&gt;SOQL/DML inside a loop&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Log is huge but missing the actual error &lt;/td&gt;&lt;td&gt;Log level set to FINEST, middle got truncated&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;NullPointerException with no useful detail &lt;/td&gt;&lt;td&gt;Unguarded relationship field traversal&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;User reports missing data, no error thrown &lt;/td&gt;&lt;td&gt;Sharing rules / &lt;code&gt;with sharing&lt;/code&gt; mismatch&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;System.debug&lt;/code&gt; never shows up &lt;/td&gt;&lt;td&gt;Code ran in an async context — check Apex Jobs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Passes in test, fails in prod &lt;/td&gt;&lt;td&gt;Test isolated by &lt;code&gt;startTest&lt;/code&gt;/&lt;code&gt;stopTest&lt;/code&gt; boundary&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Debugging Apex is mostly about knowing &lt;em&gt;where&lt;/em&gt; to look — the log level, the sharing context, and whether you're even looking at the right transaction. Get those three things right and most &quot;mystery bugs&quot; stop being mysterious.&lt;/p&gt;
&lt;p&gt;What's the Apex bug that took you the longest to track down? Drop it in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29142/debugging-salesforce-apex-8-gotchas-that-waste-everyones-weekend</guid>
<pubDate>Tue, 29 Sep 2026 18:45:24 +0000</pubDate>
</item>
<item>
<title>60 % of our image decode failures were HEIC files named .jpg</title>
<link>https://coderlegion.com/28863/60-of-our-image-decode-failures-were-heic-files-named-jpg</link>
<description>&lt;p&gt;For a few days our analytics showed a class of failure that made no sense: images rejected by the decoder in well under a second. Not slow failures, not out-of-memory on a huge photo — instant refusals. Between 2026-09-06 and 09-10, &lt;strong&gt;60 % of every &lt;code&gt;decode_failed&lt;/code&gt; event came from the same shape of file: an iPhone photo called &lt;code&gt;photo.jpg&lt;/code&gt;, reporting &lt;code&gt;image/jpeg&lt;/code&gt;, whose actual bytes were HEIC.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We had a HEIC path. It was never tried, because the gate that decides whether to load the HEIC decoder was reading the filename.&lt;/p&gt;
&lt;p&gt;Disclosure: this is from &lt;a rel=&quot;nofollow&quot; href=&quot;https://lensup.ai/&quot;&gt;LensUp&lt;/a&gt;, which is ours. The fix is the boring one — read the bytes — but the interesting part is where naive byte-sniffing then went wrong, and the precedence rule we ended up needing.&lt;/p&gt;
&lt;h3 id=&quot;how-a-heic-ends-up-called-jpg&quot;&gt;How a HEIC ends up called .jpg&lt;/h3&gt;
&lt;p&gt;Nobody renames these on purpose. The path is mundane: an iPhone shoots HEIC, you share the photo through a messenger or copy it to a PC, and somewhere along the way the transport relabels it. The extension becomes &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;file.type&lt;/code&gt; becomes &lt;code&gt;image/jpeg&lt;/code&gt;, and the container is still HEIC.&lt;/p&gt;
&lt;p&gt;From the browser's side there is no hint of this. &lt;code&gt;File.type&lt;/code&gt; is not derived from content — it is whatever the OS or the transport asserted. So:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Cheap, synchronous gate so the page can decide whether to lazy-load the HEIC decoder
// module at all. Byte-level truth lives elsewhere — names and MIME lie often enough
// that this is only a candidate check, never a verdict.
export function isHeicCandidate(file) {
  const mime = file?.type?.toLowerCase();
  if (mime === 'image/heic' || mime === 'image/heif') return true;
  const extension = file?.name?.split('.').pop()?.toLowerCase();
  return extension === 'heic' || extension === 'heif';
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That function is fine — as a hint for whether to &lt;em&gt;lazy-load&lt;/em&gt; a wasm decoder you do not want to ship to everyone. It is not fine as the thing that decides which decoder runs. That was the bug: a hint had been promoted to a verdict.&lt;/p&gt;
&lt;h3 id=&quot;sniffing-twelve-to-sixteen-bytes-never-the-file&quot;&gt;Sniffing: twelve to sixteen bytes, never the file&lt;/h3&gt;
&lt;p&gt;The fix reads a small slice. Not the file — a slice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const FTYP_READ_BYTES = 64;
export async function sniffInputSignature(file) {
  let head;
  try {
    head = new Uint8Array(await file.slice(0, FTYP_READ_BYTES).arrayBuffer());
  } catch { return null; }
  if (head.length &amp;lt; 4) return null;
  const ascii = (from, to) =&amp;gt; String.fromCharCode(...head.subarray(from, Math.min(to, head.length)));

  if (head[0] === 0xff &amp;amp;&amp;amp; head[1] === 0xd8 &amp;amp;&amp;amp; head[2] === 0xff) return 'jpeg';
  if (head[0] === 0x89 &amp;amp;&amp;amp; ascii(1, 4) === 'PNG') return 'png';
  if (ascii(0, 4) === 'GIF8') return 'gif';
  if (ascii(0, 4) === 'RIFF' &amp;amp;&amp;amp; head.length &amp;gt;= 12 &amp;amp;&amp;amp; ascii(8, 12) === 'WEBP') return 'webp';
  if (ascii(0, 2) === 'BM') return 'bmp';
  if (ascii(0, 4) === '%PDF') return 'pdf';
  // … TIFF (II*\0 / MM\0*), then the ISO-BMFF family below
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sixteen bytes covers every format the importer accepts, and &lt;code&gt;file.slice()&lt;/code&gt; means the read is one small range request against the blob rather than a load of a 12-megapixel photo.&lt;/p&gt;
&lt;h3 id=&quot;where-just-read-the-bytes-is-not-enough&quot;&gt;Where &quot;just read the bytes&quot; is not enough&lt;/h3&gt;
&lt;p&gt;ISO base media files — HEIC, HEIF, AVIF, MP4 and friends — all start the same way: bytes 4–8 spell &lt;code&gt;ftyp&lt;/code&gt;, bytes 8–12 carry the &lt;strong&gt;major brand&lt;/strong&gt;. So the naive version is: read the major brand, map &lt;code&gt;heic&lt;/code&gt; → HEIC decoder, &lt;code&gt;avif&lt;/code&gt; → native.&lt;/p&gt;
&lt;p&gt;Then review found the hole. Two of those brands are generic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const HEIC_BRANDS = new Set(['heic', 'heix', 'hevc', 'hevx', 'heim', 'heis', 'hevm', 'hevs']);
const AVIF_BRANDS = new Set(['avif', 'avis']);
const GENERIC_HEIF_BRANDS = new Set(['mif1', 'msf1']);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mif1&lt;/code&gt; and &lt;code&gt;msf1&lt;/code&gt; mean &quot;this is a HEIF container&quot; and say &lt;strong&gt;nothing about the codec inside&lt;/strong&gt;. An AVIF file may carry &lt;code&gt;mif1&lt;/code&gt; as its major brand and declare &lt;code&gt;avif&lt;/code&gt; only further down, in the compatible-brands list. Treat &lt;code&gt;mif1&lt;/code&gt; as HEIC and you hand a file the browser decodes natively — losslessly, instantly — to a lossy wasm transcode. You have recompressed somebody's photo for no reason.&lt;/p&gt;
&lt;p&gt;So a generic major brand gets resolved from the compatible brands, and the &lt;code&gt;ftyp&lt;/code&gt; box is walked using its own declared size:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if (head.length &amp;gt;= 12 &amp;amp;&amp;amp; ascii(4, 8) === 'ftyp') {
  const major = ascii(8, 12).trim().toLowerCase();
  if (HEIC_BRANDS.has(major)) return 'heic';
  if (AVIF_BRANDS.has(major)) return 'avif';
  if (GENERIC_HEIF_BRANDS.has(major)) {
    // ftyp box: [size:4]['ftyp'][major:4][minor version:4][compatible brands:4 each …]
    const boxSize = ((head[0] &amp;lt;&amp;lt; 24) | (head[1] &amp;lt;&amp;lt; 16) | (head[2] &amp;lt;&amp;lt; 8) | head[3]) &amp;gt;&amp;gt;&amp;gt; 0;
    const end = Math.min(head.length, boxSize || head.length);
    const compatible = [];
    for (let offset = 16; offset + 4 &amp;lt;= end; offset += 4) {
      compatible.push(ascii(offset, offset + 4).trim().toLowerCase());
    }
    if (compatible.some((b) =&amp;gt; AVIF_BRANDS.has(b))) return 'avif';
    if (compatible.some((b) =&amp;gt; HEIC_BRANDS.has(b))) return 'heic';
    return 'heif';   // a HEIF container that names no codec
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two details in there that are easy to skip. The box size is clamped with &lt;code&gt;Math.min(head.length, …)&lt;/code&gt; so a bogus length cannot walk past the slice we actually read. And when no compatible brand is specific, the answer is the honest &lt;code&gt;'heif'&lt;/code&gt; — &quot;a HEIF container, codec unknown&quot; — rather than a guess.&lt;/p&gt;
&lt;h3 id=&quot;the-precedence-rule-bytes-win-the-label-breaks-ties&quot;&gt;The precedence rule: bytes win, the label breaks ties&lt;/h3&gt;
&lt;p&gt;&quot;Always trust the bytes&quot; is the slogan, and it is &lt;em&gt;almost&lt;/em&gt; right. The generic-HEIF case is where it breaks: a file whose bytes only say &lt;code&gt;heif&lt;/code&gt; could be a HEIC the browser cannot open, or something it can. There is no byte that settles it.&lt;/p&gt;
&lt;p&gt;So the rule that shipped is narrower and, I think, the actually correct one: &lt;strong&gt;bytes win over the label; the label breaks ties only when the bytes say nothing.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export async function classifyInput(file) {
  const signature = await sniffInputSignature(file);
  const labelPdf  = isPdfCandidate(file);
  const labelHeic = isHeicCandidate(file);
  const labelTiff = isTiffCandidate(file);
  const isPdf  = signature === 'pdf'  || (labelPdf  &amp;amp;&amp;amp; signature === null);
  const isHeic = !isPdf &amp;amp;&amp;amp; (signature === 'heic' || (labelHeic &amp;amp;&amp;amp; (signature === null || signature === 'heif')));
  const isTiff = !isPdf &amp;amp;&amp;amp; !isHeic &amp;amp;&amp;amp; (signature === 'tiff' || (labelTiff &amp;amp;&amp;amp; signature === null));
  return { signature, isPdf, isHeic, isTiff, isImage: !isPdf &amp;amp;&amp;amp; !isHeic &amp;amp;&amp;amp; !isTiff,
           mislabelled: labelPdf &amp;amp;&amp;amp; IMAGE_SIGNATURES.has(signature) };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the three interesting rows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scan.pdf&lt;/code&gt; whose bytes are a JPEG is an image&lt;/strong&gt;, not a broken PDF. Before this, those went to the PDF parser and produced &lt;code&gt;pdf_invalid&lt;/code&gt;: 39 of them between 09-06 and 09-11, most from Windows desktops retrying the same file over and over — the user's file was fine, our routing was not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IMG_0001.jpg&lt;/code&gt; whose bytes start with &lt;code&gt;%PDF&lt;/code&gt; is a PDF.&lt;/strong&gt; Same rule, other direction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A generic HEIF goes to the HEIC decoder only if the label agrees.&lt;/strong&gt; &lt;code&gt;signature === 'heif' &amp;amp;&amp;amp; labelHeic&lt;/code&gt; → HEIC. A &lt;code&gt;.jpg&lt;/code&gt; with those bytes stays on the native path, because that is the case where the label is the only evidence that exists.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;one-more-lie-application-octet-stream&quot;&gt;One more lie: application/octet-stream&lt;/h3&gt;
&lt;p&gt;Worth its own line, because it is not a type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// application/octet-stream is what browsers say when they do not know — treat it as
// undeclared so the extension map (and downstream byte sniffing) can decide.
if (declared &amp;amp;&amp;amp; declared !== 'application/octet-stream') return declared;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows file pickers hand over &lt;code&gt;.pdf&lt;/code&gt; files with an empty or &lt;code&gt;application/octet-stream&lt;/code&gt; type often enough that treating it as a real MIME type poisons every branch downstream. It means &quot;no opinion&quot;, and the code should say so.&lt;/p&gt;
&lt;p&gt;The same file also needs a small alias table, because the same format arrives under several names: &lt;code&gt;image/pjpeg&lt;/code&gt;, &lt;code&gt;image/x-ms-bmp&lt;/code&gt;, &lt;code&gt;image/x-png&lt;/code&gt;, &lt;code&gt;image/jpg&lt;/code&gt;. Normalise first, decide second.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-take-from-this-1&quot;&gt;What I would take from this&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A cheap label check and an expensive byte check are different functions and should be named differently.&lt;/strong&gt; Our bug was one function doing both jobs — a lazy-load hint named like a verdict. &lt;code&gt;isHeicCandidate&lt;/code&gt; and &lt;code&gt;isHeicFile&lt;/code&gt; now sit next to each other with comments saying which is which, and that naming is doing more work than the sniffer is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Read the bytes&quot; needs a tie-break rule, not just a preference.&lt;/strong&gt; Some formats genuinely do not identify their own contents. Write the precedence down explicitly — bytes, then label, then a documented default — rather than letting it emerge from the order of your &lt;code&gt;if&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failures that are too fast are a routing smell.&lt;/strong&gt; A decoder that gives up in 200 ms did not struggle with your file; it was handed the wrong file. That timing was the clue that pointed at the label, and it is the one I will look for first next time.&lt;/p&gt;
&lt;p&gt;If you want to see the byte path do its thing, drop a HEIC — renamed or not — into &lt;a rel=&quot;nofollow&quot; href=&quot;https://lensup.ai/cam-scanner/&quot;&gt;a browser-based document scanner&lt;/a&gt;. It runs in the tab and files are never uploaded.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28863/60-of-our-image-decode-failures-were-heic-files-named-jpg</guid>
<pubDate>Mon, 28 Sep 2026 05:02:05 +0000</pubDate>
</item>
<item>
<title>What usually breaks first in production: code, data, or dependencies?</title>
<link>https://coderlegion.com/28777/what-usually-breaks-first-in-production-code-data-or-dependencies</link>
<description>&lt;p&gt;A service can pass every test and still fail in production. The first real issue often comes from one of these:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      unexpected data
      slow or failing dependencies
      retry behavior
      configuration drift
      traffic spikes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your experience, what breaks first most often? And what signal helps you find it fastest?&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/28777/what-usually-breaks-first-in-production-code-data-or-dependencies</guid>
<pubDate>Sun, 27 Sep 2026 06:16:57 +0000</pubDate>
</item>
<item>
<title>Building for the Person Who Can't Debug It</title>
<link>https://coderlegion.com/28482/building-for-the-person-who-cant-debug-it</link>
<description>&lt;p&gt;This is the second half of a two-part writeup on an n8n lead-intake workflow. &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/27289/the-trigger-swap-that-broke-every-downstream-reference&quot;&gt;Part1&lt;/a&gt; covered the Webhook payload break — this one covers the rest of the build.&lt;/p&gt;
&lt;p&gt;The first post about this workflow covered the Webhook payload break — the moment the Form Trigger got swapped out and every downstream reference silently pointed at data that no longer existed. That part's done. This is the rest of the build: three smaller decisions that don't share a bug, but do share a reason.&lt;/p&gt;
&lt;p&gt;Marisol — the client this workflow was built for — isn't technical. She has no developer on call. If this breaks while she's using it, she's the one looking at it first. That fact didn't come up once and get filed away; it kept showing up, in places that had nothing to do with each other.&lt;/p&gt;
&lt;p&gt;Flipping the IF Node&lt;/p&gt;
&lt;p&gt;The junk filter was supposed to do one thing: stop a submission if both phone and email were empty, since a lead with neither is useless. The logic in my head was straightforward — &quot;if phone empty AND email empty, stop.&quot;&lt;/p&gt;
&lt;p&gt;But when I actually built the IF node, I flipped it: &quot;if phone OR email is not empty, continue.&quot; Same condition, inverted. What changed was which branch reads as the main path. Written this way, the true branch is the branch I actually follow when I trace the workflow — the valid data moving forward. The false branch is just &quot;nothing happens,&quot; which is exactly what it should look like: the door that quietly doesn't open.&lt;/p&gt;
&lt;p&gt;It's a small thing. The workflow does the same job either way. But if Marisol — or anyone else — ever opens this node to understand why a lead didn't show up in Sheets, the condition should read like the thing that's actually happening, not like a double negative she has to mentally invert first.&lt;/p&gt;
&lt;p&gt;Tested both directions: a submission with both phone and email empty correctly stopped, and a submission with just one filled correctly passed through.&lt;/p&gt;
&lt;p&gt;Redesigning the Set Node&lt;/p&gt;
&lt;p&gt;Originally, the Set node had exactly one job — default the message field to &quot;No message provided&quot; if it came in empty. That was true right up until the trigger swap.&lt;/p&gt;
&lt;p&gt;Once the Webhook replaced the Form Trigger, the payload picked up a lot of extra weight — headers, params, webhookUrl, executionMode, all the raw HTTP metadata that comes bundled with a webhook request. None of it was useful to Sheets or Slack. All of it was now sitting in the same object as the five fields that actually mattered.&lt;/p&gt;
&lt;p&gt;I could have left it. The extra keys wouldn't have broken anything downstream — Sheets and Slack only read what I told them to read. But that's exactly the kind of thing that's invisible until someone else has to look at it. So I rebuilt the Set node to do more: explicit mappings for all five clean fields — Name, Phone, Email, Service address, Message — stripping the noise and defaulting the message in the same place.&lt;/p&gt;
&lt;p&gt;I chose explicit field mappings over n8n's &quot;keep only set fields&quot; toggle on purpose. The toggle does the same job with less typing, but it hides what's happening — you'd have to open the node and go find the setting to know why the extra fields disappeared. Written out explicitly, the five fields the workflow actually cares about are right there, visible, nothing to go dig for.&lt;/p&gt;
&lt;p&gt;Writing the Doc for Marisol&lt;/p&gt;
&lt;p&gt;The last piece was the plain-English explanation — the thing Marisol actually reads if something goes wrong. First time writing a doc like this, so I built it around what she'd need, not around what I found interesting to explain: what the workflow does, a step-by-step walkthrough using the real node names (Filter Out Junk Leads, Clean and Format Lead Data — the names she'd see if she ever opened it herself), and a troubleshooting section.&lt;/p&gt;
&lt;p&gt;For troubleshooting, I didn't invent hypothetical failure cases — I used one that had actually happened. During testing, the Google Sheets node failed once because the credential needed reconnecting; disconnecting and reconnecting it fixed it. Small thing, but real, and more likely to recur than something exotic I could've made up instead.&lt;/p&gt;
&lt;p&gt;The doc and the exported workflow JSON stayed as two separate files, deliberately. The JSON is the actual automation, importable straight into her n8n. The doc is a reference for a human. They don't get merged into each other — that split is what the brief asked for, and forcing them together would've made one file try to do two jobs.&lt;/p&gt;
&lt;p&gt;Same Constraint, Three Places&lt;/p&gt;
&lt;p&gt;None of these three decisions are related on the surface — an inverted condition, a redesigned node, a document. What ties them together is that each one got shaped by a person who wasn't going to be in the room the next time this workflow needed explaining, and who can't reason through n8n's internals if it breaks. The workflow doesn't get simpler because of that. It gets more legible.&lt;/p&gt;
&lt;p&gt;More on how this and other builds come together: tarek-elzoghby.github.io/tarek-elzoghby-hub&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28482/building-for-the-person-who-cant-debug-it</guid>
<pubDate>Thu, 24 Sep 2026 13:19:38 +0000</pubDate>
</item>
<item>
<title>How to Fix Firebase `PERMISSION_DENIED` Errors in React Native</title>
<link>https://coderlegion.com/28157/how-to-fix-firebase-permissiondenied-errors-in-react-native</link>
<description>&lt;p&gt;If you are using Firebase with a React Native app, sooner or later you will probably run into something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Error: PERMISSION_DENIED: Permission denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the annoying part is that the error does not tell you much.&lt;/p&gt;
&lt;p&gt;It does not say:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Your Firestore rule on this collection is blocking the request.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It just tells you that Firebase rejected it.&lt;/p&gt;
&lt;p&gt;That can happen for several different reasons. Your Firestore rules might be doing exactly what you told them to do, your user might not actually be authenticated, App Check might be involved, or your app might be connected to a different Firebase project than you think.&lt;/p&gt;
&lt;p&gt;So before changing random rules until the error disappears, it is better to figure out &lt;strong&gt;why Firebase is rejecting the request&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-find-out-which-firebase-service-is-rejecting-the-request&quot;&gt;First, find out which Firebase service is rejecting the request&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PERMISSION_DENIED&lt;/code&gt; is not limited to Firestore.&lt;/p&gt;
&lt;p&gt;You can see permission-related errors with services such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud Firestore&lt;/li&gt;
&lt;li&gt;Realtime Database&lt;/li&gt;
&lt;li&gt;Firebase Storage&lt;/li&gt;
&lt;li&gt;Firebase App Check&lt;/li&gt;
&lt;li&gt;Firebase Authentication-related operations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So start by looking at the full error.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the stack trace shows a Firestore operation such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;firestore()
  .collection(&quot;users&quot;)
  .doc(userId)
  .get();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then your first place to look is Firestore security rules.&lt;/p&gt;
&lt;p&gt;If the error happens while uploading a file, Storage rules are more relevant.&lt;/p&gt;
&lt;p&gt;This sounds obvious, but a long React Native stack trace can make it easy to focus on the wrong part.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-firestore-security-rules&quot;&gt;Check your Firestore security rules&lt;/h3&gt;
&lt;p&gt;If the error comes from Firestore, check your rules first.&lt;/p&gt;
&lt;p&gt;For example, you might have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {

    match /users/{userId} {
      allow read, write: if request.auth != null;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This rule only allows authenticated users to access documents under:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/{userId}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your app makes the request before the user has signed in, Firebase will reject it.&lt;/p&gt;
&lt;p&gt;That can produce:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;permission-denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is that Firebase is not necessarily malfunctioning here.&lt;/p&gt;
&lt;p&gt;Your rule may simply be doing exactly what it was written to do.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-the-user-is-actually-authenticated&quot;&gt;Check whether the user is actually authenticated&lt;/h3&gt;
&lt;p&gt;One of the easiest mistakes is assuming that the user is signed in when the request runs.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const user = auth().currentUser;

console.log(user);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If that prints:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then there is no authenticated Firebase user at that moment.&lt;/p&gt;
&lt;p&gt;A rule such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if request.auth != null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will reject the request.&lt;/p&gt;
&lt;p&gt;This can happen when your app starts up and immediately tries to read Firestore before Firebase Authentication has restored the user's session.&lt;/p&gt;
&lt;p&gt;For example, you might have code that effectively does this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const user = auth().currentUser;

firestore()
  .collection(&quot;users&quot;)
  .doc(user.uid)
  .get();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;user&lt;/code&gt; is not available yet, the request can fail.&lt;/p&gt;
&lt;p&gt;Instead, make sure your application waits for the authentication state before making requests that require authentication.&lt;/p&gt;
&lt;p&gt;With React Native Firebase, you can listen for authentication changes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;useEffect(() =&amp;gt; {
  const unsubscribe = auth().onAuthStateChanged(user =&amp;gt; {
    if (user) {
      // Safe to make requests that require authentication
    }
  });

  return unsubscribe;
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact structure depends on your app, but the idea is the same:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not assume authentication has finished restoring just because the app has started.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-that-the-rule-matches-the-path-you-are-actually-using&quot;&gt;Check that the rule matches the path you are actually using&lt;/h3&gt;
&lt;p&gt;This is another very common one.&lt;/p&gt;
&lt;p&gt;Imagine your rules contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read, write: if request.auth != null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But your app actually accesses:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/profiles/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That rule does not apply to &lt;code&gt;/profiles/123&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need a matching rule for the path being accessed.&lt;/p&gt;
&lt;p&gt;This becomes especially confusing when you have nested collections.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/123/settings/theme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not the same path as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your rules need to match the actual document path.&lt;/p&gt;
&lt;p&gt;If you are getting &lt;code&gt;PERMISSION_DENIED&lt;/code&gt;, look at the exact collection and document your code is trying to access.&lt;/p&gt;
&lt;p&gt;Do not assume the rule you are looking at is the one Firebase is evaluating.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;be-careful-with-nested-firestore-documents&quot;&gt;Be careful with nested Firestore documents&lt;/h3&gt;
&lt;p&gt;Suppose you have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;users/{userId}/settings/{settingId}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read, write: if request.auth != null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and expect that to cover everything underneath the user.&lt;/p&gt;
&lt;p&gt;It does not automatically mean that every nested document is covered.&lt;/p&gt;
&lt;p&gt;You may need a recursive wildcard depending on the structure and rules you want.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId}/{document=**} {
  allow read, write: if request.auth != null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Be careful with this kind of rule, though.&lt;/p&gt;
&lt;p&gt;It can give access to much more data than you intended.&lt;/p&gt;
&lt;p&gt;Do not add a broad wildcard just because it makes the error disappear.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;do-not-use-allow-read-write-if-true-as-the-final-fix&quot;&gt;Do not use &lt;code&gt;allow read, write: if true&lt;/code&gt; as the final fix&lt;/h3&gt;
&lt;p&gt;You will sometimes see people recommend this while debugging:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, it can make the permission error disappear.&lt;/p&gt;
&lt;p&gt;It also allows anyone who can access your Firebase project from the client to read and write the matching data.&lt;/p&gt;
&lt;p&gt;That is not a real fix.&lt;/p&gt;
&lt;p&gt;If you temporarily use an open rule to confirm that the rules are the cause, put the secure rule back immediately afterward.&lt;/p&gt;
&lt;p&gt;For example, if the app should only allow authenticated users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if request.auth != null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is very different from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal is not simply to make Firebase stop complaining.&lt;/p&gt;
&lt;p&gt;The goal is to make the rule match what your application is actually supposed to allow.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-you-are-using-the-correct-firebase-project&quot;&gt;Check whether you are using the correct Firebase project&lt;/h3&gt;
&lt;p&gt;This one can waste a lot of time.&lt;/p&gt;
&lt;p&gt;Your React Native app might be connected to a different Firebase project than the one you are looking at in the Firebase Console.&lt;/p&gt;
&lt;p&gt;You think you are checking the rules for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-production-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but the app is actually connected to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-development-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you change rules in the console and nothing happens.&lt;/p&gt;
&lt;p&gt;The app keeps returning:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;permission-denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;because you are fixing the wrong project.&lt;/p&gt;
&lt;p&gt;Check your Firebase configuration and make sure the project ID matches the Firebase project you are inspecting.&lt;/p&gt;
&lt;p&gt;For Android, check the Firebase configuration used by your Android app.&lt;/p&gt;
&lt;p&gt;For iOS, check the corresponding iOS Firebase configuration.&lt;/p&gt;
&lt;p&gt;If you have development, staging, and production projects, this becomes especially important.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-android-and-ios-firebase-configuration&quot;&gt;Check your Android and iOS Firebase configuration&lt;/h3&gt;
&lt;p&gt;If the problem only happens on one platform, compare the Firebase configuration between them.&lt;/p&gt;
&lt;p&gt;For Android, make sure the correct Firebase configuration is being used.&lt;/p&gt;
&lt;p&gt;For iOS, make sure the correct configuration is being used there as well.&lt;/p&gt;
&lt;p&gt;A situation like this is possible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Android → Firebase project A
iOS     → Firebase project B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then spend hours changing Firestore rules in project A while testing an iOS build that is actually talking to project B.&lt;/p&gt;
&lt;p&gt;If the error happens only on Android or only on iOS, platform-specific Firebase configuration should be on your checklist.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-firebase-app-check&quot;&gt;Check Firebase App Check&lt;/h3&gt;
&lt;p&gt;App Check can also be involved when Firebase rejects requests.&lt;/p&gt;
&lt;p&gt;App Check is designed to help verify that requests are coming from your legitimate app rather than an unauthorized client.&lt;/p&gt;
&lt;p&gt;If you recently enabled App Check or changed its configuration, check whether the failing request is related to App Check.&lt;/p&gt;
&lt;p&gt;This becomes especially important if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the app worked before enabling App Check&lt;/li&gt;
&lt;li&gt;requests work in one environment but not another&lt;/li&gt;
&lt;li&gt;you are testing a development build&lt;/li&gt;
&lt;li&gt;you changed App Check enforcement settings&lt;/li&gt;
&lt;li&gt;you changed the app's Android or iOS configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not immediately disable App Check just because it is involved.&lt;/p&gt;
&lt;p&gt;First determine whether the request is failing because of App Check configuration or because of your Firestore/Storage rules.&lt;/p&gt;
&lt;p&gt;Those are different problems.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;development-and-production-can-behave-differently&quot;&gt;Development and production can behave differently&lt;/h3&gt;
&lt;p&gt;A common source of confusion is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;It works in development, but my production build gets &lt;code&gt;PERMISSION_DENIED&lt;/code&gt;.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That does not necessarily mean the Firebase rules randomly changed.&lt;/p&gt;
&lt;p&gt;Your development and production builds may be using different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firebase projects&lt;/li&gt;
&lt;li&gt;application IDs&lt;/li&gt;
&lt;li&gt;bundle identifiers&lt;/li&gt;
&lt;li&gt;configuration files&lt;/li&gt;
&lt;li&gt;App Check settings&lt;/li&gt;
&lt;li&gt;authentication settings&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the problem only happens in a release build, compare the Firebase configuration between the working and failing builds.&lt;/p&gt;
&lt;p&gt;This is usually more useful than changing your Firestore rules blindly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-the-exact-user-id-being-used&quot;&gt;Check the exact user ID being used&lt;/h3&gt;
&lt;p&gt;Suppose your rule is intended to allow users to access their own documents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read, write: if request.auth.uid == userId;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means this request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will only work if:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;request.auth.uid == &quot;123&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your application accidentally requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while the logged-in user is actually &lt;code&gt;123&lt;/code&gt;, Firebase will reject it.&lt;/p&gt;
&lt;p&gt;So log the values you are using while debugging:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(&quot;Current user:&quot;, auth().currentUser?.uid);
console.log(&quot;Requested user:&quot;, userId);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If they do not match, you have found the problem.&lt;/p&gt;
&lt;p&gt;This is much more useful than changing the security rule to allow everyone.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-storage-rules-if-the-error-happens-during-uploads&quot;&gt;Check your Storage rules if the error happens during uploads&lt;/h3&gt;
&lt;p&gt;If the problem happens while uploading an image, PDF, or another file, you may actually be dealing with Firebase Storage rather than Firestore.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FirebaseError: [storage/unauthorized] User is not authorized to perform the desired action.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storage has its own security rules.&lt;/p&gt;
&lt;p&gt;A Firestore rule like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if request.auth != null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;does not control Firebase Storage.&lt;/p&gt;
&lt;p&gt;If the error happens during:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;storage()
  .ref(&quot;profile-images/avatar.jpg&quot;)
  .putFile(filePath);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;look at your Storage rules instead.&lt;/p&gt;
&lt;p&gt;The service producing the error matters.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-realtime-database-rules-separately&quot;&gt;Check Realtime Database rules separately&lt;/h3&gt;
&lt;p&gt;The same idea applies to Realtime Database.&lt;/p&gt;
&lt;p&gt;Realtime Database has its own rules structure, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;rules&quot;: {
    &quot;.read&quot;: &quot;auth != null&quot;,
    &quot;.write&quot;: &quot;auth != null&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changing Firestore rules will not fix a Realtime Database permission problem.&lt;/p&gt;
&lt;p&gt;If your application uses several Firebase services, make sure you know which one is rejecting the request before changing anything.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;use-the-firebase-emulator-when-appropriate&quot;&gt;Use the Firebase Emulator when appropriate&lt;/h3&gt;
&lt;p&gt;If you are working on complicated security rules, the Firebase Emulator Suite can make debugging much easier.&lt;/p&gt;
&lt;p&gt;Instead of repeatedly testing rules against production data, you can test your rules locally.&lt;/p&gt;
&lt;p&gt;This is especially useful when your rules depend on things like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;request.auth
request.auth.uid
request.resource.data
resource.data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, a rule might look correct at first glance but fail because the incoming document does not contain the field your rule expects.&lt;/p&gt;
&lt;p&gt;Testing the rule with known authentication and data values makes these problems much easier to see.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;look-at-what-changed-right-before-the-error&quot;&gt;Look at what changed right before the error&lt;/h3&gt;
&lt;p&gt;This is one of the most useful debugging habits for Firebase errors.&lt;/p&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What changed immediately before &lt;code&gt;PERMISSION_DENIED&lt;/code&gt; appeared?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Maybe you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;changed Firestore rules&lt;/li&gt;
&lt;li&gt;added authentication&lt;/li&gt;
&lt;li&gt;changed the user document structure&lt;/li&gt;
&lt;li&gt;switched Firebase projects&lt;/li&gt;
&lt;li&gt;enabled App Check&lt;/li&gt;
&lt;li&gt;changed an Android package name&lt;/li&gt;
&lt;li&gt;changed an iOS bundle identifier&lt;/li&gt;
&lt;li&gt;updated a Firebase dependency&lt;/li&gt;
&lt;li&gt;moved a Firestore collection&lt;/li&gt;
&lt;li&gt;changed the document ID&lt;/li&gt;
&lt;li&gt;created a new production build&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The timing does not prove what caused the error, but it gives you a very good place to start.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-simple-debugging-order&quot;&gt;A simple debugging order&lt;/h3&gt;
&lt;p&gt;When I get a Firebase &lt;code&gt;PERMISSION_DENIED&lt;/code&gt; error, I usually go through these checks:&lt;/p&gt;
&lt;h4 id=&quot;1-identify-the-firebase-service&quot;&gt;1. Identify the Firebase service&lt;/h4&gt;
&lt;p&gt;Is it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firestore?&lt;/li&gt;
&lt;li&gt;Storage?&lt;/li&gt;
&lt;li&gt;Realtime Database?&lt;/li&gt;
&lt;li&gt;App Check?&lt;/li&gt;
&lt;li&gt;something else?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-read-the-exact-path&quot;&gt;2. Read the exact path&lt;/h4&gt;
&lt;p&gt;What collection, document, or storage path is the app trying to access?&lt;/p&gt;
&lt;h4 id=&quot;3-check-authentication&quot;&gt;3. Check authentication&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(auth().currentUser?.uid);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is there actually a logged-in user?&lt;/p&gt;
&lt;h4 id=&quot;4-check-the-security-rule&quot;&gt;4. Check the security rule&lt;/h4&gt;
&lt;p&gt;Does the rule actually match the path being accessed?&lt;/p&gt;
&lt;h4 id=&quot;5-check-the-values-used-by-the-rule&quot;&gt;5. Check the values used by the rule&lt;/h4&gt;
&lt;p&gt;If the rule compares user IDs, roles, or document fields, verify those values.&lt;/p&gt;
&lt;h4 id=&quot;6-check-the-firebase-project&quot;&gt;6. Check the Firebase project&lt;/h4&gt;
&lt;p&gt;Make sure the app and Firebase Console are using the same project.&lt;/p&gt;
&lt;h4 id=&quot;7-check-app-check&quot;&gt;7. Check App Check&lt;/h4&gt;
&lt;p&gt;Especially if the problem started after enabling or enforcing it.&lt;/p&gt;
&lt;h4 id=&quot;8-check-platform-specific-configuration&quot;&gt;8. Check platform-specific configuration&lt;/h4&gt;
&lt;p&gt;If only Android or iOS is affected, compare the configuration for that platform.&lt;/p&gt;
&lt;h4 id=&quot;9-reproduce-the-problem-again&quot;&gt;9. Reproduce the problem again&lt;/h4&gt;
&lt;p&gt;After making one change, test again.&lt;/p&gt;
&lt;p&gt;This gives you a much clearer debugging trail.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-finding-a-simple-rules-mistake&quot;&gt;Example: finding a simple rules mistake&lt;/h3&gt;
&lt;p&gt;Imagine your Firestore rule says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read: if request.auth != null
               &amp;amp;&amp;amp; request.auth.uid == userId;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your app is logged in as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;abc123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but your code requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Firebase sees:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;request.auth.uid == &quot;abc123&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;userId == &quot;undefined&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The condition is false.&lt;/p&gt;
&lt;p&gt;So Firebase returns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;permission-denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is not Firebase.&lt;/p&gt;
&lt;p&gt;The problem is that your application generated the wrong document path.&lt;/p&gt;
&lt;p&gt;This is why checking the exact request is so important.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-2&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PERMISSION_DENIED&lt;/code&gt; can be frustrating because the error itself is often very short while the number of possible causes is not.&lt;/p&gt;
&lt;p&gt;The important thing is to avoid treating it as simply:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Firebase permissions are broken.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Start by finding out &lt;strong&gt;which Firebase service rejected the request&lt;/strong&gt;, then check the exact path, authentication state, security rules, and Firebase project.&lt;/p&gt;
&lt;p&gt;If the error appeared after a recent change, use that as a clue.&lt;/p&gt;
&lt;p&gt;And avoid the temptation to solve everything with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That only hides the real problem and can leave your data exposed.&lt;/p&gt;
&lt;p&gt;If your error is buried inside a much larger React Native/Firebase log and it is difficult to tell whether the problem is authentication, rules, App Check, configuration, or something else, you can paste the full error into &lt;strong&gt;FixMyError&lt;/strong&gt; and use it to narrow down what is actually going wrong.&lt;/p&gt;
&lt;p&gt;Try it at &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;https://www.fixmyerrorapp.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/28157/how-to-fix-firebase-permissiondenied-errors-in-react-native</guid>
<pubDate>Tue, 22 Sep 2026 12:05:03 +0000</pubDate>
</item>
<item>
<title>Android System Back Button Closes the App After Upgrading to Target SDK 36 — React Native Fix</title>
<link>https://coderlegion.com/28094/android-system-back-button-closes-the-app-after-upgrading-to-target-sdk-36-react-native-fix</link>
<description>&lt;p&gt;If you're maintaining a React Native Android application and recently upgraded your project to &lt;strong&gt;target SDK 36 (Android 16)&lt;/strong&gt;, you may encounter an unexpected navigation issue:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Pressing the Android system back button closes the app instead of navigating to the previous screen.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This can be particularly confusing when React Navigation is already configured correctly and the same navigation flow works perfectly on older Android target SDK versions.&lt;/p&gt;
&lt;p&gt;This article explains what changed, how to identify the issue, the workaround I used, and what you should consider for a proper Android 16 migration.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;After upgrading the Android project from an older target SDK to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;targetSdkVersion = 36
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the app started behaving differently when using the &lt;strong&gt;Android system back button&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, suppose the navigation stack is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
  ↓
Products
  ↓
Product Details
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the user presses the Android system back button from &lt;strong&gt;Product Details&lt;/strong&gt;, the expected behavior is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Product Details
        ↓
    Products
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, the application could close.&lt;/p&gt;
&lt;p&gt;Interestingly, navigation using React Navigation itself continued to work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;navigation.goBack();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The issue was specifically related to the &lt;strong&gt;Android system back event&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;environment&quot;&gt;Environment&lt;/h2&gt;
&lt;p&gt;The issue was observed in a React Native application with a setup similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React Native
React Navigation
Android SDK 36
Target SDK 36
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important change was upgrading the Android target SDK to &lt;strong&gt;API level 36&lt;/strong&gt;, corresponding to Android 16.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-does-this-happen-&quot;&gt;Why Does This Happen?&lt;/h2&gt;
&lt;p&gt;The important part of the migration is the Android back-navigation change.&lt;/p&gt;
&lt;p&gt;Android has been moving away from the older:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;onBackPressed()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;mechanism toward the newer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OnBackInvokedDispatcher
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and predictive back navigation APIs.&lt;/p&gt;
&lt;p&gt;With newer Android versions and target SDK requirements, applications need to properly participate in the newer back-navigation system.&lt;/p&gt;
&lt;p&gt;This can expose compatibility issues in applications or libraries that still rely on the older back handling mechanism.&lt;/p&gt;
&lt;p&gt;In a React Native application, the Android system back event eventually needs to be correctly propagated into the React Native / React Navigation layer.&lt;/p&gt;
&lt;p&gt;If that doesn't happen, Android may interpret the back action as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;No navigation handler
        ↓
Finish current Activity
        ↓
App closes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;System Back
     ↓
React Native Back Handler
     ↓
React Navigation
     ↓
Previous screen
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-i-identified-the-problem&quot;&gt;How I Identified the Problem&lt;/h2&gt;
&lt;p&gt;The first thing I checked was whether React Navigation itself was working.&lt;/p&gt;
&lt;p&gt;Navigation from the application was working correctly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;navigation.navigate(&quot;ProductDetails&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and programmatic back navigation also worked:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;navigation.goBack();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem only occurred when pressing the &lt;strong&gt;physical/system Android back button&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I also checked whether the application was closing directly from the Home screen.&lt;/p&gt;
&lt;p&gt;That behavior is expected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
 ↓
Android Back
 ↓
App exits
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual problem was:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
 ↓
Product List
 ↓
Product Details
 ↓
Android Back
 ↓
App exits ❌
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
 ↓
Product List
 ↓
Product Details
 ↓
Android Back
 ↓
Product List ✅
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This distinction is important when debugging Android back navigation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-androidmanifest-configuration&quot;&gt;The AndroidManifest Configuration&lt;/h2&gt;
&lt;p&gt;The Android application can control the behavior of the newer back system using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;android:enableOnBackInvokedCallback
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;application
    android:name=&quot;.MainApplication&quot;
    android:label=&quot;@string/app_name&quot;
    android:icon=&quot;@mipmap/ic_launcher&quot;
    android:enableOnBackInvokedCallback=&quot;false&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, setting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;android:enableOnBackInvokedCallback=&quot;false&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;worked as a temporary workaround.&lt;/p&gt;
&lt;p&gt;After adding this configuration, the Android system back button once again started navigating to the previous React Navigation screen instead of immediately closing the application.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-does-false-work-&quot;&gt;Why Does &lt;code&gt;false&lt;/code&gt; Work?&lt;/h2&gt;
&lt;p&gt;Setting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;android:enableOnBackInvokedCallback=&quot;false&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;essentially disables the application's participation in the newer &lt;code&gt;OnBackInvokedDispatcher&lt;/code&gt; behavior.&lt;/p&gt;
&lt;p&gt;This allows the application to continue using the older back handling path.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;h4 id=&quot;without-the-workaround&quot;&gt;Without the workaround&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Android System Back
        ↓
OnBackInvokedDispatcher
        ↓
React Native / Navigation handler
        ↓
❌ Handler not properly triggered
        ↓
Activity finishes
        ↓
App closes
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;with-the-temporary-workaround&quot;&gt;With the temporary workaround&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Android System Back
        ↓
Legacy back handling
        ↓
React Native BackHandler
        ↓
React Navigation
        ↓
Previous screen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the workaround doesn't really &quot;fix&quot; predictive back support.&lt;/p&gt;
&lt;p&gt;It tells Android to avoid the newer callback behavior for this application.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;is-enableonbackinvokedcallback-false-the-permanent-fix-&quot;&gt;Is &lt;code&gt;enableOnBackInvokedCallback=&quot;false&quot;&lt;/code&gt; the Permanent Fix?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Not necessarily.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is an important distinction.&lt;/p&gt;
&lt;p&gt;If your application is targeting Android 16, you should ideally investigate whether your React Native version and navigation dependencies properly support Android's newer back-navigation behavior.&lt;/p&gt;
&lt;p&gt;Using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;android:enableOnBackInvokedCallback=&quot;false&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;can be useful as a compatibility workaround while migrating, but it should not automatically be considered the final Android 16 solution.&lt;/p&gt;
&lt;p&gt;The long-term goal should be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Android 16
      ↓
Predictive Back
      ↓
OnBackInvokedDispatcher
      ↓
React Native
      ↓
React Navigation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;working correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;check-your-react-native-version&quot;&gt;Check Your React Native Version&lt;/h2&gt;
&lt;p&gt;Before adding application-level workarounds, check the React Native version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm list react-native
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn why react-native
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also check React Navigation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm list @react-navigation/native
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;react-native
@react-navigation/native
@react-navigation/native-stack
@react-navigation/bottom-tabs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Older combinations may have different levels of Android predictive-back support.&lt;/p&gt;
&lt;p&gt;It's important to evaluate the complete dependency chain rather than changing only the AndroidManifest.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;check-your-mainactivity&quot;&gt;Check Your &lt;code&gt;MainActivity&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Also inspect your Android &lt;code&gt;MainActivity&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A typical React Native application may have something similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class MainActivity extends ReactActivity {

    @Override
    protected String getMainComponentName() {
        return &quot;MyApp&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your application has custom back handling, search the Android project for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;onBackPressed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -R &quot;onBackPressed&quot; android/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also search for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OnBackInvokedDispatcher
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OnBackInvokedCallback
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps identify whether custom native code is interfering with the new Android back-navigation mechanism.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;check-react-native-backhandler&quot;&gt;Check React Native &lt;code&gt;BackHandler&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;React Native applications can listen for Android back events using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { BackHandler } from &quot;react-native&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;useEffect(() =&amp;gt; {
  const subscription = BackHandler.addEventListener(
    &quot;hardwareBackPress&quot;,
    () =&amp;gt; {
      navigation.goBack();
      return true;
    }
  );

  return () =&amp;gt; subscription.remove();
}, [navigation]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, &lt;strong&gt;don't add this globally just to hide the problem&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;React Navigation normally handles Android back navigation for its navigation stack.&lt;/p&gt;
&lt;p&gt;Adding multiple global &lt;code&gt;BackHandler&lt;/code&gt; listeners can introduce new problems such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Back event being consumed too early&lt;/li&gt;
&lt;li&gt;Incorrect navigation&lt;/li&gt;
&lt;li&gt;Screens being popped multiple times&lt;/li&gt;
&lt;li&gt;Modals not closing correctly&lt;/li&gt;
&lt;li&gt;Navigation stack becoming inconsistent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So first determine whether the underlying Android back event is reaching React Native correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;a-useful-debugging-test&quot;&gt;A Useful Debugging Test&lt;/h2&gt;
&lt;p&gt;You can temporarily add a &lt;code&gt;BackHandler&lt;/code&gt; listener to determine whether React Native receives the system back event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;useEffect(() =&amp;gt; {
  const subscription = BackHandler.addEventListener(
    &quot;hardwareBackPress&quot;,
    () =&amp;gt; {
      console.log(&quot;ANDROID BACK EVENT RECEIVED&quot;);

      return false;
    }
  );

  return () =&amp;gt; subscription.remove();
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then navigate to a screen and press the Android system back button.&lt;/p&gt;
&lt;p&gt;If you see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ANDROID BACK EVENT RECEIVED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then the event is reaching React Native.&lt;/p&gt;
&lt;p&gt;If the app closes without the event reaching your JavaScript handler, the problem is likely further down in the Android back-dispatching path.&lt;/p&gt;
&lt;p&gt;This is a useful way to separate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Android back event problem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React Navigation configuration problem
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;don-t-confuse-home-screen-behavior-with-a-bug&quot;&gt;Don't Confuse Home Screen Behavior With a Bug&lt;/h2&gt;
&lt;p&gt;One important testing detail:&lt;/p&gt;
&lt;p&gt;If the user is already on the root screen:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pressing Android Back is normally expected to exit the application.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
 ↓
Android Back
 ↓
App closes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That alone isn't evidence of a problem.&lt;/p&gt;
&lt;p&gt;The important test is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
 ↓
Screen A
 ↓
Screen B
 ↓
Android Back
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Screen B
 ↓
Screen A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Screen B
 ↓
App closes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then back navigation is not being handled correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;testing-checklist-after-target-sdk-36-upgrade&quot;&gt;Testing Checklist After Target SDK 36 Upgrade&lt;/h2&gt;
&lt;p&gt;After upgrading to API 36, I recommend testing all of these scenarios.&lt;/p&gt;
&lt;h4 id=&quot;1-normal-navigation&quot;&gt;1. Normal navigation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home → List → Details
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press system Back.&lt;/p&gt;
&lt;p&gt;Expected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Details → List
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-multiple-navigation-levels&quot;&gt;2. Multiple navigation levels&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home → A → B → C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press Back repeatedly.&lt;/p&gt;
&lt;p&gt;Expected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;C → B → A → Home
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-root-screen&quot;&gt;3. Root screen&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Home
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Press Back.&lt;/p&gt;
&lt;p&gt;Expected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App exits
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-modal&quot;&gt;4. Modal&lt;/h4&gt;
&lt;p&gt;Open a modal and press Back.&lt;/p&gt;
&lt;p&gt;Expected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Modal closes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App exits
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-bottom-tabs&quot;&gt;5. Bottom tabs&lt;/h4&gt;
&lt;p&gt;Test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tab A → Tab B → Screen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and verify that system Back behaves consistently with your navigation design.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-authentication-flow&quot;&gt;6. Authentication flow&lt;/h4&gt;
&lt;p&gt;Test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Login → OTP → Home
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and make sure Back doesn't allow users to incorrectly return to authentication screens.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;7-deep-links&quot;&gt;7. Deep links&lt;/h4&gt;
&lt;p&gt;Test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Deep Link → Details
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and verify the back stack behaves as expected.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;8-android-gesture-navigation&quot;&gt;8. Android gesture navigation&lt;/h4&gt;
&lt;p&gt;Test using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3-button navigation&lt;/li&gt;
&lt;li&gt;Gesture navigation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Predictive back behavior can differ from traditional button-based testing, so both should be tested.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;temporary-workaround&quot;&gt;Temporary Workaround&lt;/h2&gt;
&lt;p&gt;If your application is currently affected and you need to stabilize the release, one possible temporary configuration is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;application
    ...
    android:enableOnBackInvokedCallback=&quot;false&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After rebuilding the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
cd ..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then rebuild:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native run-android
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or use your normal release build process.&lt;/p&gt;
&lt;p&gt;For an Expo bare/prebuild project, make sure the generated Android configuration is also correctly reflected in the source configuration you use for builds, rather than relying on a manual change that can be overwritten.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;recommended-migration-approach&quot;&gt;Recommended Migration Approach&lt;/h2&gt;
&lt;p&gt;Instead of treating the manifest flag as the final solution, I recommend approaching the migration in this order:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. Upgrade target SDK → 36
          ↓
2. Test system Back
          ↓
3. Check React Native version
          ↓
4. Check React Navigation versions
          ↓
5. Search for custom Android back handling
          ↓
6. Search for BackHandler overrides
          ↓
7. Test predictive back
          ↓
8. Update incompatible dependencies
          ↓
9. Use manifest workaround if required
          ↓
10. Remove workaround after proper migration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach makes it easier to identify the actual compatibility problem.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;important-takeaway&quot;&gt;Important Takeaway&lt;/h2&gt;
&lt;p&gt;Upgrading:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;targetSdkVersion
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not always just a build configuration change.&lt;/p&gt;
&lt;p&gt;A target SDK upgrade can activate or expose new Android platform behavior.&lt;/p&gt;
&lt;p&gt;For Android 16 / API 36, &lt;strong&gt;back navigation and predictive back behavior are especially important areas to test&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If your React Native app suddenly starts closing when the Android system back button is pressed after moving to target SDK 36, don't immediately assume that your React Navigation stack is broken.&lt;/p&gt;
&lt;p&gt;First determine whether:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Android
   ↓
Back Dispatcher
   ↓
React Native
   ↓
React Navigation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is working correctly.&lt;/p&gt;
&lt;p&gt;The following configuration can serve as a temporary compatibility workaround:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;android:enableOnBackInvokedCallback=&quot;false&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but the preferred long-term solution is to ensure that the React Native and navigation stack properly supports Android's modern back-navigation APIs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-checklist&quot;&gt;Final Checklist&lt;/h2&gt;
&lt;p&gt;Before releasing a target SDK 36 build, verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Android system Back navigates correctly&lt;/li&gt;
&lt;li&gt;[ ] Root screen exits correctly&lt;/li&gt;
&lt;li&gt;[ ] Nested navigation works&lt;/li&gt;
&lt;li&gt;[ ] Modals handle Back correctly&lt;/li&gt;
&lt;li&gt;[ ] Bottom tabs behave correctly&lt;/li&gt;
&lt;li&gt;[ ] Authentication screens behave correctly&lt;/li&gt;
&lt;li&gt;[ ] Deep links create the expected back stack&lt;/li&gt;
&lt;li&gt;[ ] Gesture navigation works&lt;/li&gt;
&lt;li&gt;[ ] 3-button navigation works&lt;/li&gt;
&lt;li&gt;[ ] Predictive back behavior has been tested&lt;/li&gt;
&lt;li&gt;[ ] React Native version is compatible&lt;/li&gt;
&lt;li&gt;[ ] React Navigation dependencies are compatible&lt;/li&gt;
&lt;li&gt;[ ] Custom &lt;code&gt;BackHandler&lt;/code&gt; logic has been reviewed&lt;/li&gt;
&lt;li&gt;[ ] Native &lt;code&gt;onBackPressed()&lt;/code&gt; implementations have been reviewed&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;OnBackInvokedDispatcher&lt;/code&gt; usage has been reviewed&lt;/li&gt;
&lt;li&gt;[ ] Any temporary &lt;code&gt;enableOnBackInvokedCallback&lt;/code&gt; workaround is documented&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Android 16 introduces another step in the evolution of Android back navigation. For React Native applications, upgrading to &lt;strong&gt;target SDK 36&lt;/strong&gt; can expose problems where the system Back button no longer reaches the JavaScript navigation layer as expected.&lt;/p&gt;
&lt;p&gt;If your app starts closing instead of navigating backward, investigate the new Android back-dispatching behavior before modifying your navigation stack.&lt;/p&gt;
&lt;p&gt;A manifest-level workaround such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;android:enableOnBackInvokedCallback=&quot;false&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;may restore the previous behavior temporarily, but dependency compatibility and proper predictive-back support should be addressed as part of the complete migration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Target SDK upgrades should always be followed by dedicated navigation regression testing—not just build and installation testing.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;✍️ Written by &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/dainyjose&quot;&gt;&lt;strong&gt;Dainy Jose&lt;/strong&gt;&lt;/a&gt; — React Native Mobile Application Developer with 3+ years of experience building cross-platform mobile apps using &lt;strong&gt;React Native (Expo, TypeScript, Redux)&lt;/strong&gt;. &lt;br&gt;
Currently expanding backend knowledge through the &lt;strong&gt;MERN Stack (MongoDB, Express.js, React.js, Node.js)&lt;/strong&gt; to create more efficient, full-stack mobile experiences.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Tech Stack:&lt;/strong&gt; React Native · TypeScript · Redux · Expo · Firebase · Node.js · Express.js · MongoDB · REST API · JWT · Jest · Google Maps · Razorpay · PayU · Agile · SDLC · Git · Bitbucket · Jira &lt;/p&gt;
&lt;h2 id=&quot;section&quot;&gt;&lt;/h2&gt;
&lt;p&gt; &lt;strong&gt;Connect with me&lt;/strong&gt;:&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://dainyjose.github.io/my-portfolio/&quot;&gt;Portfolio&lt;/a&gt;&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/dainyjose&quot;&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/dainyjose&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28094/android-system-back-button-closes-the-app-after-upgrading-to-target-sdk-36-react-native-fix</guid>
<pubDate>Tue, 22 Sep 2026 04:30:24 +0000</pubDate>
</item>
<item>
<title>The Firebase Auth bug that took me 3 days to find (it wasn't Firebase's fault)</title>
<link>https://coderlegion.com/27717/the-firebase-auth-bug-that-took-me-3-days-to-find-it-wasnt-firebases-fault</link>
<description>&lt;p&gt;&lt;strong&gt;The symptom&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;signInWithRedirect would fire fine — the Google popup flow completed, the redirect came back, and then... nothing. auth.currentUser was null. No error in the console. No failed network request. The user object had just vanished somewhere between the redirect and my app reading it.&lt;/p&gt;
&lt;p&gt;The frustrating part: it worked perfectly on localhost. It only broke in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The wrong turns&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First guess: async timing bug. I wrapped the auth-state check in every useEffect variation I could think of, added loading states, tried onAuthStateChanged instead of reading currentUser directly. No change.&lt;/p&gt;
&lt;p&gt;Second guess: a config mismatch. I diffed my Firebase config between local and prod character by character. Identical.&lt;/p&gt;
&lt;p&gt;Third guess: CORS. Checked network tab, no CORS errors, no failed requests at all — which in hindsight should have been the clue. A CORS problem fails loudly. This was failing silently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What was actually happening&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Firebase's redirect-based auth flow works by briefly using an iframe on your authDomain (typically your-project.firebaseapp.com) to relay the session back to your app. That relay depends on third-party storage being accessible inside that iframe — cookies/localStorage set on firebaseapp.com, read while your actual site is on a different domain.&lt;/p&gt;
&lt;p&gt;Browsers have been steadily tightening third-party storage partitioning (Safari's been strict about this for years, Chrome's been catching up). Production was on my real custom domain. Local dev was hitting localhost, which a lot of browsers still treat more permissively. That's why it &quot;worked&quot; locally and silently died in prod — the iframe's storage access was just being partitioned away, and Firebase had no clean way to surface that as an error since, from its perspective, the browser just... didn't return anything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Switch to signInWithPopup instead of signInWithRedirect. Popup-based auth doesn't rely on that same cross-domain iframe storage relay — the popup and the opener communicate directly, sidestepping the whole partitioning problem.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Before: silently broke in production
await signInWithRedirect(auth, provider);

// After: works consistently across browsers
await signInWithPopup(auth, provider);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trade-off: popups get blocked more aggressively on mobile and by some ad blockers, so it's not a strictly-better default — just a better fit for what was actually failing here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The lesson&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When something works locally and silently fails in production with zero error output, my new first suspect is cross-origin storage behavior, not application logic. Silent failure is often the browser deciding not to tell you it blocked something, rather than your code doing something wrong.&lt;/p&gt;
&lt;p&gt;Anyone else hit this exact wall with Firebase (or any auth provider using a similar iframe-relay pattern)? Curious if others landed on popup, or found a way to keep redirect working reliably.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27717/the-firebase-auth-bug-that-took-me-3-days-to-find-it-wasnt-firebases-fault</guid>
<pubDate>Sat, 19 Sep 2026 08:24:54 +0000</pubDate>
</item>
<item>
<title>The Trigger Swap That Broke Every Downstream Reference</title>
<link>https://coderlegion.com/27289/the-trigger-swap-that-broke-every-downstream-reference</link>
<description>&lt;p&gt;I built this workflow — a lead intake automation for a made-up client, Ferrer Window Co. — with a Form Trigger first, even though I knew from the start it needed to end up on a Webhook. That wasn't the plan drifting. It was deliberate: I'd used webhooks before and Form Trigger less, so building with Form Trigger first let me see real data coming through without also holding webhook payload structure in my head at the same time. Get the logic right, then swap the trigger once everything else was solid.&lt;/p&gt;
&lt;p&gt;And the logic did get solid. By the time I was ready to swap, I had a full chain working: an IF node filtering out junk leads (both phone and email empty), a Set node defaulting empty message fields to &quot;No message provided,&quot; and Sheets and Slack both branching off that same cleaned data independently. Tested both edge cases — junk lead correctly stopped, empty message correctly defaulted. Everything passed.&lt;/p&gt;
&lt;p&gt;Then I deleted the Form Trigger, added a Webhook node, and sent a test payload through Postman.&lt;/p&gt;
&lt;p&gt;It broke immediately.&lt;/p&gt;
&lt;p&gt;Not the logic — the logic was fine. What broke was every single reference to the data. With Form Trigger, the payload had come through flat:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;Name&quot;: &quot;Tarek&quot;,
  &quot;Phone&quot;: &quot;02-011-41104-582&quot;,
  &quot;Email&quot;: &quot;*Emails are not allowed*&quot;,
  &quot;Service address&quot;: &quot;Primary school st&quot;,
  &quot;Message&quot;: &quot;I want to request an order&quot;,
  &quot;submittedAt&quot;: &quot;2026-08-24T03:24:50.089-04:00&quot;,
  &quot;formMode&quot;: &quot;test&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With Webhook, everything I actually wanted was nested one level down, sitting alongside a pile of raw HTTP metadata I didn't need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;headers&quot;: {...},
  &quot;body&quot;: {
    &quot;Name&quot;: &quot;Tarek&quot;,
    &quot;Phone&quot;: &quot;02-011-41104-582&quot;,
    ...
  },
  &quot;webhookUrl&quot;: &quot;...&quot;,
  &quot;executionMode&quot;: &quot;test&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every reference I'd written — the IF node's condition, the Set node's field mappings — pointed at &lt;code&gt;$json.Name&lt;/code&gt;, &lt;code&gt;$json.Phone&lt;/code&gt;, and so on. None of it was wrong, exactly. It was just pointed at a shape of data that no longer existed. The whole workflow's reasoning was intact and now unreachable.&lt;/p&gt;
&lt;p&gt;So I went back through it node by node — the IF node's conditions, the Set node's mappings — and repointed every one from &lt;code&gt;$json.X&lt;/code&gt; to &lt;code&gt;$json.body.X&lt;/code&gt;. Nothing about &lt;em&gt;what&lt;/em&gt; the workflow decided changed. Only &lt;em&gt;where it looked&lt;/em&gt; for the thing it was deciding about.&lt;/p&gt;
&lt;p&gt;Once that was done, I didn't just assume it worked because the references compiled. I re-ran both edge cases through the webhook path — both phone and email empty, and an empty message field — to confirm the filter still stopped junk leads and the default still kicked in, now that the data was arriving through a different shape entirely.&lt;/p&gt;
&lt;p&gt;The part I'd flag for anyone doing this same swap later: it's not a dramatic failure. Nothing throws a loud error that points you at the fix. The workflow just quietly stops seeing the data it expects, because the trigger node changed the shape of what &quot;the data&quot; even means. If you built your logic against one trigger's payload structure, swapping the trigger isn't a drop-in replacement — it's a second pass through everything downstream, checking what each node thinks it's looking at.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27289/the-trigger-swap-that-broke-every-downstream-reference</guid>
<pubDate>Tue, 15 Sep 2026 13:15:42 +0000</pubDate>
</item>
<item>
<title>How to Fix Expo Doctor Errors in Expo</title>
<link>https://coderlegion.com/26393/how-to-fix-expo-doctor-errors-in-expo</link>
<description>&lt;p&gt;If you work with Expo long enough, you will probably run into &lt;code&gt;expo-doctor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and instead of everything being green, you get something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check Expo config
✖ Check dependencies
✖ Check package versions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes there is one problem.&lt;/p&gt;
&lt;p&gt;Sometimes there are several.&lt;/p&gt;
&lt;p&gt;And sometimes the output looks much worse than the actual problem.&lt;/p&gt;
&lt;p&gt;The good thing is that &lt;code&gt;expo-doctor&lt;/code&gt; is usually pretty clear about what it does not like. The tricky part is knowing what the warnings actually mean and which ones you need to fix.&lt;/p&gt;
&lt;p&gt;Here is how I usually work through Expo Doctor errors without immediately changing half of the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-run-expo-doctor-by-itself&quot;&gt;First, run Expo Doctor by itself&lt;/h3&gt;
&lt;p&gt;Start with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let it finish and read the full output.&lt;/p&gt;
&lt;p&gt;Do not immediately start changing dependencies just because you see a red &lt;code&gt;✖&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Expo Doctor runs several checks, and a failure in one check does not necessarily mean your entire project is broken.&lt;/p&gt;
&lt;p&gt;For example, you might see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check that packages match versions required by installed Expo SDK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is very different from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check for common project configuration problems
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first one points toward dependency versions.&lt;/p&gt;
&lt;p&gt;The second could require looking at your Expo configuration.&lt;/p&gt;
&lt;p&gt;So the first step is simply figuring out &lt;strong&gt;which check actually failed&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-expo-sdk-version&quot;&gt;Check your Expo SDK version&lt;/h3&gt;
&lt;p&gt;A lot of Expo Doctor problems come down to package versions not matching the Expo SDK you are using.&lt;/p&gt;
&lt;p&gt;You can check your Expo version in &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;dependencies&quot;: {
    &quot;expo&quot;: &quot;~54.0.0&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact version will depend on your project.&lt;/p&gt;
&lt;p&gt;You can also run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo config --type public
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important thing is to know which Expo SDK your project is actually using before changing dependencies.&lt;/p&gt;
&lt;p&gt;This matters because Expo packages are not completely independent of the SDK.&lt;/p&gt;
&lt;p&gt;You cannot always just install the newest version of every package and expect everything to work together.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;fix-packages-that-are-on-the-wrong-version&quot;&gt;Fix packages that are on the wrong version&lt;/h3&gt;
&lt;p&gt;One of the most common Expo Doctor messages is related to package versions.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check that packages match versions required by installed Expo SDK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expo may tell you that some packages are expected to be on different versions.&lt;/p&gt;
&lt;p&gt;If that happens, do not manually guess which version to install.&lt;/p&gt;
&lt;p&gt;For Expo-managed projects, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expo can use your current SDK to install compatible package versions.&lt;/p&gt;
&lt;p&gt;You can also check the project with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install --check
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it reports packages that are out of date, you can usually let Expo correct them with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install --fix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is generally safer than doing something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install package-name@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;because the latest package version is not necessarily the version your current Expo SDK expects.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;do-not-blindly-update-everything&quot;&gt;Do not blindly update everything&lt;/h3&gt;
&lt;p&gt;This is worth mentioning because it is an easy mistake to make.&lt;/p&gt;
&lt;p&gt;Suppose Expo Doctor tells you that three packages have version mismatches.&lt;/p&gt;
&lt;p&gt;You might think:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Fine, I'll just update everything.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then you run something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and suddenly you have five new errors.&lt;/p&gt;
&lt;p&gt;You have now changed many things at once, which makes it harder to figure out what actually caused the problem.&lt;/p&gt;
&lt;p&gt;If Expo is telling you that a package should be on a specific version, start with that package.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install react-native-screens
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rather than manually searching for the newest version and installing it.&lt;/p&gt;
&lt;p&gt;Make one meaningful change at a time when possible.&lt;/p&gt;
&lt;p&gt;It makes debugging much easier.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-package-json-for-old-dependencies&quot;&gt;Check &lt;code&gt;package.json&lt;/code&gt; for old dependencies&lt;/h3&gt;
&lt;p&gt;Another common situation is an old dependency that you no longer use.&lt;/p&gt;
&lt;p&gt;Over time, a project can collect packages from old features, experiments, tutorials, or libraries you tried and later removed.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;package.json&lt;/code&gt; might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;dependencies&quot;: {
    &quot;some-old-package&quot;: &quot;^1.2.3&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;even though nothing in your application imports it anymore.&lt;/p&gt;
&lt;p&gt;If Expo Doctor complains about that package, first check whether you still need it.&lt;/p&gt;
&lt;p&gt;Search your project for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;some-old-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there are no imports or references and you know you do not need it anymore, removing the dependency may be the correct fix.&lt;/p&gt;
&lt;p&gt;For npm:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm uninstall some-old-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Yarn:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn remove some-old-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run Expo Doctor again.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-for-packages-that-are-not-compatible-with-your-project&quot;&gt;Check for packages that are not compatible with your project&lt;/h3&gt;
&lt;p&gt;Sometimes the problem is not simply that a package is old.&lt;/p&gt;
&lt;p&gt;A package may require native code or a setup that does not fit the way your Expo project is configured.&lt;/p&gt;
&lt;p&gt;This becomes especially important when adding React Native libraries that were designed with bare React Native projects in mind.&lt;/p&gt;
&lt;p&gt;You may install a package successfully with npm, but that does not automatically mean your Expo project is configured to use it correctly.&lt;/p&gt;
&lt;p&gt;If Expo Doctor starts complaining immediately after adding a new library, that timing is useful information.&lt;/p&gt;
&lt;p&gt;Try to remember:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What changed right before the error appeared?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you just installed a package and Expo Doctor started failing, investigate that package first.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-expo-configuration&quot;&gt;Check your Expo configuration&lt;/h3&gt;
&lt;p&gt;Expo Doctor can also complain about your project configuration.&lt;/p&gt;
&lt;p&gt;Depending on your setup, this information may live in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.config.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.config.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, your configuration might contain something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;expo&quot;: {
    &quot;name&quot;: &quot;My App&quot;,
    &quot;slug&quot;: &quot;my-app&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you recently changed your configuration, added a plugin, changed an Android or iOS setting, or installed a package that requires an Expo config plugin, check those changes carefully.&lt;/p&gt;
&lt;p&gt;A configuration problem can sometimes look like a dependency problem because several parts of the Expo toolchain depend on the same project configuration.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-expo-config-plugins&quot;&gt;Check Expo config plugins&lt;/h3&gt;
&lt;p&gt;Config plugins are another area that can cause confusion.&lt;/p&gt;
&lt;p&gt;If you have something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;expo&quot;: {
    &quot;plugins&quot;: [
      &quot;some-package&quot;
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;make sure that package is actually installed and that the plugin is valid for your current setup.&lt;/p&gt;
&lt;p&gt;A common situation is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You install a package.&lt;/li&gt;
&lt;li&gt;You add its plugin to the Expo configuration.&lt;/li&gt;
&lt;li&gt;Later, you remove the package.&lt;/li&gt;
&lt;li&gt;The plugin entry stays behind.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now Expo may still try to process something that no longer exists.&lt;/p&gt;
&lt;p&gt;If you recently removed a native package, check &lt;code&gt;app.json&lt;/code&gt; or your app config for leftover plugin entries.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-for-duplicate-dependencies&quot;&gt;Check for duplicate dependencies&lt;/h3&gt;
&lt;p&gt;Another useful thing to investigate is duplicate versions of the same package.&lt;/p&gt;
&lt;p&gt;For example, your project might end up with different parts of the dependency tree using different versions of a React Native package.&lt;/p&gt;
&lt;p&gt;You can inspect the dependency tree with npm:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or check a specific package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm ls react-native-screens
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see multiple versions where you expected one, that can be a clue.&lt;/p&gt;
&lt;p&gt;This does not automatically mean that multiple versions are wrong. Some projects legitimately have them.&lt;/p&gt;
&lt;p&gt;The important part is to understand &lt;strong&gt;why&lt;/strong&gt; they are there before trying to force everything onto one version.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-react-native-version-too&quot;&gt;Check your React Native version too&lt;/h3&gt;
&lt;p&gt;Expo SDK and React Native versions are closely related.&lt;/p&gt;
&lt;p&gt;If you manually changed your React Native version, or upgraded one without properly upgrading the Expo SDK around it, Expo Doctor may start reporting compatibility problems.&lt;/p&gt;
&lt;p&gt;For example, if your &lt;code&gt;package.json&lt;/code&gt; contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;dependencies&quot;: {
    &quot;expo&quot;: &quot;~54.0.0&quot;,
    &quot;react-native&quot;: &quot;...&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;make sure those versions belong together.&lt;/p&gt;
&lt;p&gt;If you are upgrading Expo, follow the Expo upgrade process instead of manually changing a bunch of package versions and hoping they line up afterward.&lt;/p&gt;
&lt;p&gt;A partially upgraded project is often harder to fix than a project that was left alone.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;run-expo-doctor-again-after-fixing-something&quot;&gt;Run &lt;code&gt;expo-doctor&lt;/code&gt; again after fixing something&lt;/h3&gt;
&lt;p&gt;This sounds obvious, but it is an important part of the process.&lt;/p&gt;
&lt;p&gt;After making a change, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;again.&lt;/p&gt;
&lt;p&gt;You want to know whether the change actually fixed the problem.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Before:
✖ Check package versions

After:
✔ Check package versions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you know that particular problem is gone.&lt;/p&gt;
&lt;p&gt;If another check is still failing, you can work on that one separately.&lt;/p&gt;
&lt;p&gt;This is much easier than making ten changes and running Expo Doctor once at the end.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-if-expo-doctor-still-fails-&quot;&gt;What if Expo Doctor still fails?&lt;/h3&gt;
&lt;p&gt;If you have corrected the obvious dependency and configuration problems but Expo Doctor is still reporting an error, look at the &lt;strong&gt;exact check that is failing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check for common project configuration problems
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not enough information by itself.&lt;/p&gt;
&lt;p&gt;Read the lines underneath it.&lt;/p&gt;
&lt;p&gt;Expo Doctor normally provides additional information about what it found.&lt;/p&gt;
&lt;p&gt;That information is much more useful than the &lt;code&gt;✖&lt;/code&gt; itself.&lt;/p&gt;
&lt;p&gt;Copy the complete section of the output, not just the first line.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;clear-the-cache-if-the-project-still-behaves-strangely&quot;&gt;Clear the cache if the project still behaves strangely&lt;/h3&gt;
&lt;p&gt;Clearing caches is not usually the first fix for an Expo Doctor warning.&lt;/p&gt;
&lt;p&gt;Expo Doctor is checking your project configuration and dependencies, so a cache reset will not magically fix an incorrect package version.&lt;/p&gt;
&lt;p&gt;However, if you have already fixed the reported problem and the project is still behaving as if the old state exists, clearing the Metro cache can help.&lt;/p&gt;
&lt;p&gt;For Expo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is particularly useful after changing dependencies, imports, or other files that Metro may have cached.&lt;/p&gt;
&lt;p&gt;Just keep in mind that &lt;strong&gt;clearing Metro's cache and fixing Expo Doctor are two different things&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-reinstalling-node-modules-makes-sense&quot;&gt;When reinstalling &lt;code&gt;node_modules&lt;/code&gt; makes sense&lt;/h3&gt;
&lt;p&gt;If your package configuration looks correct but your installation itself seems broken, reinstalling dependencies can be worth trying.&lt;/p&gt;
&lt;p&gt;Delete:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then reinstall:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if necessary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would not delete your lockfile automatically.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;package-lock.json&lt;/code&gt; or &lt;code&gt;yarn.lock&lt;/code&gt; records the dependency versions that your project is using. Removing it can cause a completely new dependency tree to be installed, which may introduce more changes than you wanted.&lt;/p&gt;
&lt;p&gt;If you specifically need to regenerate the lockfile, that is a different situation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;be-careful-when-upgrading-an-expo-project&quot;&gt;Be careful when upgrading an Expo project&lt;/h3&gt;
&lt;p&gt;If the reason for your Expo Doctor errors is that you are trying to move to a newer Expo SDK, do not treat it like a normal dependency update.&lt;/p&gt;
&lt;p&gt;An Expo SDK upgrade can involve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expo packages&lt;/li&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;native dependencies&lt;/li&gt;
&lt;li&gt;config plugins&lt;/li&gt;
&lt;li&gt;Android configuration&lt;/li&gt;
&lt;li&gt;iOS configuration&lt;/li&gt;
&lt;li&gt;other packages tied to the SDK&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you upgrade only one or two pieces, you can end up with a project where everything is technically installed but the versions do not belong together.&lt;/p&gt;
&lt;p&gt;If your goal is specifically to upgrade Expo, follow the upgrade process for the target SDK rather than trying to fix every warning individually.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-simple-way-to-approach-expo-doctor&quot;&gt;A simple way to approach Expo Doctor&lt;/h3&gt;
&lt;p&gt;When I get an Expo Doctor error, I usually work through it in this order:&lt;/p&gt;
&lt;h4 id=&quot;1-run-expo-doctor&quot;&gt;1. Run Expo Doctor&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-find-the-failed-check&quot;&gt;2. Find the failed check&lt;/h4&gt;
&lt;p&gt;Do not worry about every line yet. Find the actual &lt;code&gt;✖&lt;/code&gt; checks.&lt;/p&gt;
&lt;h4 id=&quot;3-read-the-details-underneath&quot;&gt;3. Read the details underneath&lt;/h4&gt;
&lt;p&gt;That is usually where the useful information is.&lt;/p&gt;
&lt;h4 id=&quot;4-check-what-changed-recently&quot;&gt;4. Check what changed recently&lt;/h4&gt;
&lt;p&gt;Did you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;install a package?&lt;/li&gt;
&lt;li&gt;update Expo?&lt;/li&gt;
&lt;li&gt;update React Native?&lt;/li&gt;
&lt;li&gt;remove a package?&lt;/li&gt;
&lt;li&gt;change &lt;code&gt;app.json&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;add a config plugin?&lt;/li&gt;
&lt;li&gt;switch branches?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recent changes are often the best clue.&lt;/p&gt;
&lt;h4 id=&quot;5-fix-the-smallest-thing-first&quot;&gt;5. Fix the smallest thing first&lt;/h4&gt;
&lt;p&gt;If a package version is wrong, fix that package.&lt;/p&gt;
&lt;p&gt;If a plugin is left behind, remove it.&lt;/p&gt;
&lt;p&gt;If a dependency is no longer needed, uninstall it.&lt;/p&gt;
&lt;h4 id=&quot;6-run-expo-doctor-again&quot;&gt;6. Run Expo Doctor again&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then see what remains.&lt;/p&gt;
&lt;p&gt;This gives you a much cleaner debugging loop.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;one-example&quot;&gt;One example&lt;/h3&gt;
&lt;p&gt;Imagine you run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and get a dependency version warning.&lt;/p&gt;
&lt;p&gt;You check &lt;code&gt;package.json&lt;/code&gt; and notice that you manually installed a newer version of an Expo-related package a few days ago.&lt;/p&gt;
&lt;p&gt;Instead of immediately reinstalling everything, you can try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or, if Expo identifies the package as needing a correction:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install --fix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;again.&lt;/p&gt;
&lt;p&gt;If the check passes, you have your answer.&lt;/p&gt;
&lt;p&gt;If it does not, you now have a smaller problem to investigate.&lt;/p&gt;
&lt;p&gt;That is the part that makes debugging much easier: &lt;strong&gt;change something, test it, and use the new output to decide what to do next.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-3&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;Expo Doctor is there to help you find problems before they turn into much bigger issues.&lt;/p&gt;
&lt;p&gt;The important thing is not to treat every warning as a reason to rebuild your entire project.&lt;/p&gt;
&lt;p&gt;Start with the exact check that failed.&lt;/p&gt;
&lt;p&gt;If it is a dependency problem, check the package versions.&lt;/p&gt;
&lt;p&gt;If it is a configuration problem, check your Expo config.&lt;/p&gt;
&lt;p&gt;If you recently installed or removed a package, investigate that change.&lt;/p&gt;
&lt;p&gt;And if you are upgrading Expo, make sure you are upgrading the related dependencies together instead of manually mixing versions.&lt;/p&gt;
&lt;p&gt;Most importantly, avoid making a dozen changes at once. When you do that, even if the error disappears, you have no idea which change actually fixed it.&lt;/p&gt;
&lt;p&gt;If your Expo Doctor output is more complicated—especially when several checks fail at the same time—you can paste the full error into &lt;strong&gt;FixMyError&lt;/strong&gt; and use it to break down what the output is actually telling you.&lt;/p&gt;
&lt;p&gt;You can try it at &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;https://www.fixmyerrorapp.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/26393/how-to-fix-expo-doctor-errors-in-expo</guid>
<pubDate>Mon, 07 Sep 2026 11:14:51 +0000</pubDate>
</item>
<item>
<title>The Single Variable Rule Is As True In Debugging As It Is In A Lab</title>
<link>https://coderlegion.com/26161/the-single-variable-rule-is-as-true-in-debugging-as-it-is-in-a-lab</link>
<description>&lt;p&gt;Most of us here debug for a living, and the habit that has helped me the most did not come out of software at all, it came out of lab method. The overlap is closer than it looks, so it seemed worth writing up for this crowd.&lt;/p&gt;
&lt;h3 id=&quot;change-one-thing-then-look&quot;&gt;Change One Thing, Then Look&lt;/h3&gt;
&lt;p&gt;When a bug will not reproduce, the instinct is to bump the dependency, tweak the config, and add a retry, all in one pass. If it starts working after that, you have not fixed anything, you have hidden it. You now have four candidate causes and no way to tell them apart.&lt;/p&gt;
&lt;p&gt;Lab work has the same rule and states it more bluntly: change one variable per run. If you alter the concentration and the temperature and the container at the same time, the result cannot tell you which change mattered. Software gives us the illusion that we can skip this because iteration is cheap, but the reasoning cost is identical.&lt;/p&gt;
&lt;h3 id=&quot;write-down-the-raw-numbers-not-the-conclusion&quot;&gt;Write Down The Raw Numbers, Not The Conclusion&lt;/h3&gt;
&lt;p&gt;The other borrowed habit is the notebook. Not a summary written after the fact, the actual numbers: input, timestamp, the exact command, what came back. The value shows up on run four, when a pattern appears that no single run would have revealed.&lt;/p&gt;
&lt;p&gt;This is exactly the discipline that &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.learnhowtoscience.com/home-science-experiments/&quot;&gt;setting up a home lab&lt;/a&gt; forces on you, because there is no log aggregator to fall back on. Date, question, materials with quantities, procedure, raw results, and what surprised you. Six lines. Anyone who has tried to reconstruct a debugging session from memory a week later already knows why it matters.&lt;/p&gt;
&lt;h3 id=&quot;reproduce-three-times-before-you-believe-it&quot;&gt;Reproduce Three Times Before You Believe It&lt;/h3&gt;
&lt;p&gt;One passing run is not a fix, it is a data point. Race conditions, cache warmth, and machine state all produce green on the first try. Three clean runs, ideally on a cold start, is the cheapest form of replication available to us and it catches the fixes that were never fixes.&lt;/p&gt;
&lt;p&gt;None of this is new, it is just the scientific method with different tooling. The parts we tend to drop are the boring ones: one change at a time, raw records, and repeat before believing. Those three are what make debugging cumulative instead of a loop.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26161/the-single-variable-rule-is-as-true-in-debugging-as-it-is-in-a-lab</guid>
<pubDate>Thu, 03 Sep 2026 14:14:20 +0000</pubDate>
</item>
<item>
<title>How to Fix “Unable to Resolve Module” Errors in React Native and Expo</title>
<link>https://coderlegion.com/25620/how-to-fix-unable-to-resolve-module-errors-in-react-native-and-expo</link>
<description>&lt;h2 id=&quot;how-to-fix-unable-to-resolve-module-errors-in-react-native-and-expo&quot;&gt;How to Fix “Unable to Resolve Module” Errors in React Native and Expo&lt;/h2&gt;
&lt;p&gt;If you have been working with React Native or Expo for a while, there is a good chance you have seen an error that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ./components/Header from App.js

None of these files exist:
  * components/Header(.native|.ios|.android|.js|.jsx|.ts|.tsx)
  * components/Header/index(.native|.ios|.android|.js|.jsx|.ts|.tsx)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or maybe:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module react-native-safe-area-context from App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or the error is buried somewhere inside a much larger Metro bundler log, making it harder to see what is actually wrong.&lt;/p&gt;
&lt;p&gt;The annoying part about this error is that &lt;strong&gt;&quot;Unable to resolve module&quot; does not always mean the same thing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes the file path is wrong. Sometimes the package was never installed. Sometimes it is installed, but your dependencies are in a bad state. And sometimes everything looks correct until you notice that one letter in a filename has the wrong capitalization.&lt;/p&gt;
&lt;p&gt;I've run into all of these before.&lt;/p&gt;
&lt;p&gt;So instead of randomly deleting &lt;code&gt;node_modules&lt;/code&gt; and hoping for the best, here is how I usually work through this error.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-read-the-exact-module-metro-cannot-find&quot;&gt;First, read the exact module Metro cannot find&lt;/h3&gt;
&lt;p&gt;This sounds obvious, but the first line of the error usually tells you exactly what Metro is looking for.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ./components/Header from App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Metro is trying to find:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;./components/Header
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;relative to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That immediately gives you something to check.&lt;/p&gt;
&lt;p&gt;Ask yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does that file actually exist?&lt;/li&gt;
&lt;li&gt;Is the path correct?&lt;/li&gt;
&lt;li&gt;Is the filename spelled correctly?&lt;/li&gt;
&lt;li&gt;Are you importing it from the correct folder?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, imagine your project looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-app/
├── App.js
└── components/
    └── Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then this import should work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this will not:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./component/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One missing &lt;code&gt;s&lt;/code&gt; is enough to break the build.&lt;/p&gt;
&lt;p&gt;When the error points to a local file, I usually start here before doing anything else.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-the-file-name-and-capitalization&quot;&gt;Check the file name and capitalization&lt;/h3&gt;
&lt;p&gt;This one can be surprisingly annoying, especially if your app worked on one machine but suddenly fails during a build or after pushing the project somewhere else.&lt;/p&gt;
&lt;p&gt;Imagine your file is named:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But your import says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the operating system and environment, this can cause problems because:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;are not always treated as the same file.&lt;/p&gt;
&lt;p&gt;The same thing can happen with folders:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Components/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;vs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;components/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If Metro says it cannot find a local module and the path looks correct at first glance, check every part of the path carefully.&lt;/p&gt;
&lt;p&gt;I have lost more time than I would like to admit because of a single capital letter.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;make-sure-the-package-is-actually-installed&quot;&gt;Make sure the package is actually installed&lt;/h3&gt;
&lt;p&gt;Sometimes the missing module is not one of your own files.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that case, check whether the package exists in your project.&lt;/p&gt;
&lt;p&gt;You can look inside &lt;code&gt;package.json&lt;/code&gt; and see whether it is listed under &lt;code&gt;dependencies&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For an Expo project, it is usually better to install Expo-compatible packages with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a regular React Native project, you would normally use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn add react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact command depends on your project, but the important part is this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not assume a package is installed just because you used it in your code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is also possible that you copied code from another project, tutorial, or GitHub repository without installing all of the required dependencies.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-if-the-package-name-is-correct&quot;&gt;Check if the package name is correct&lt;/h3&gt;
&lt;p&gt;Another simple but common problem is importing the wrong package name.&lt;/p&gt;
&lt;p&gt;For example, you might write something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Something from &quot;react-native-something&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but the actual package name could be slightly different.&lt;/p&gt;
&lt;p&gt;Before reinstalling everything, check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The package name in your import.&lt;/li&gt;
&lt;li&gt;The package name in &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The package name from the package documentation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One small typo can produce the same &quot;Unable to resolve module&quot; error as a completely missing dependency.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reset-the-metro-cache&quot;&gt;Reset the Metro cache&lt;/h3&gt;
&lt;p&gt;If the file or package exists and the import looks correct, the next thing I would try is clearing Metro's cache.&lt;/p&gt;
&lt;p&gt;For Expo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For React Native, you can try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native start --reset-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Metro caches information to make development faster, but occasionally the cache gets in the way after things change.&lt;/p&gt;
&lt;p&gt;For example, you might have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;renamed a file,&lt;/li&gt;
&lt;li&gt;moved a folder,&lt;/li&gt;
&lt;li&gt;installed a new package,&lt;/li&gt;
&lt;li&gt;changed an import,&lt;/li&gt;
&lt;li&gt;switched branches in Git.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the project is still behaving like the old version exists, clearing the cache is a quick thing to try.&lt;/p&gt;
&lt;p&gt;I would not start by deleting half the project, though. Resetting the cache is much less destructive.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-node-modules&quot;&gt;Check your &lt;code&gt;node_modules&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Sometimes the dependency is listed in &lt;code&gt;package.json&lt;/code&gt;, but something went wrong during installation.&lt;/p&gt;
&lt;p&gt;This can happen after:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;switching branches,&lt;/li&gt;
&lt;li&gt;changing package versions,&lt;/li&gt;
&lt;li&gt;interrupting an installation,&lt;/li&gt;
&lt;li&gt;moving the project to another computer,&lt;/li&gt;
&lt;li&gt;or getting dependency conflicts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A common approach is to reinstall your dependencies.&lt;/p&gt;
&lt;p&gt;First, remove &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, if you use Yarn:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, start the project again and clear Metro's cache.&lt;/p&gt;
&lt;p&gt;For Expo, that could look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try to use the same package manager consistently. If your project uses &lt;code&gt;package-lock.json&lt;/code&gt;, randomly switching between npm, Yarn, and other package managers can create confusing dependency problems.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-you-are-importing-from-the-correct-location&quot;&gt;Check whether you are importing from the correct location&lt;/h3&gt;
&lt;p&gt;This is especially easy to miss after moving files around.&lt;/p&gt;
&lt;p&gt;Imagine you originally had:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── screens/
│   └── HomeScreen.js
└── components/
    └── Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside &lt;code&gt;HomeScreen.js&lt;/code&gt;, you might import:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;../components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then later, you move &lt;code&gt;HomeScreen.js&lt;/code&gt; somewhere else:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── app/
│   └── screens/
│       └── HomeScreen.js
└── components/
    └── Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The old import path may no longer be correct.&lt;/p&gt;
&lt;p&gt;You would need to update it accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;../../components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you see an &quot;Unable to resolve module&quot; error after reorganizing your project, check whether the import path still matches the new folder structure.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;be-careful-with-relative-paths&quot;&gt;Be careful with relative paths&lt;/h3&gt;
&lt;p&gt;Relative imports can become difficult to read when your project grows.&lt;/p&gt;
&lt;p&gt;You might end up with something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Something from &quot;../../../../components/Something&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At that point, it becomes much easier to accidentally use one &lt;code&gt;../&lt;/code&gt; too many or too few.&lt;/p&gt;
&lt;p&gt;If the error points to a long relative path, carefully trace it from the file where the import is written.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;../../../components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not just count the dots and slashes quickly. Actually check the location of the current file and follow the path step by step.&lt;/p&gt;
&lt;p&gt;It is boring, but it is often faster than trying random fixes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-the-package-is-compatible-with-expo&quot;&gt;Check whether the package is compatible with Expo&lt;/h3&gt;
&lt;p&gt;This is more important if you are using Expo.&lt;/p&gt;
&lt;p&gt;You may find a React Native package online, install it successfully, and then run into problems because the version does not match your Expo SDK or the package needs native setup that your project does not currently support.&lt;/p&gt;
&lt;p&gt;When possible, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install package-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of blindly installing the latest version with npm.&lt;/p&gt;
&lt;p&gt;Expo can choose a version that is compatible with your current SDK.&lt;/p&gt;
&lt;p&gt;You can also run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to check your project for dependency and configuration issues.&lt;/p&gt;
&lt;p&gt;If Metro suddenly cannot resolve something after installing or updating a package, compatibility is worth checking.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;watch-out-for-renamed-or-removed-files&quot;&gt;Watch out for renamed or removed files&lt;/h3&gt;
&lt;p&gt;Another situation that can cause this error is when your code still imports something that no longer exists.&lt;/p&gt;
&lt;p&gt;For example, you rename:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OldButton.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CustomButton.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but somewhere in the project, you still have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import OldButton from &quot;./OldButton&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The error might not happen immediately if that file is not loaded until you visit a specific screen.&lt;/p&gt;
&lt;p&gt;This is why I would search the project for the old filename.&lt;/p&gt;
&lt;p&gt;Your editor's global search is useful here. Search for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OldButton
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and see whether any old imports are still left behind.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-index-files&quot;&gt;Check &lt;code&gt;index&lt;/code&gt; files&lt;/h3&gt;
&lt;p&gt;Sometimes the missing module is actually caused by an &lt;code&gt;index.js&lt;/code&gt; or &lt;code&gt;index.ts&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;components/
├── Button/
│   ├── Button.js
│   └── index.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might import it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Button from &quot;./components/Button&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That usually depends on Metro being able to resolve the folder's &lt;code&gt;index&lt;/code&gt; file correctly.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;index.js&lt;/code&gt; was deleted, renamed, or contains a broken export, your import setup may stop working.&lt;/p&gt;
&lt;p&gt;Check whether the folder contains the file Metro expects.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export { default } from &quot;./Button&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export { Button } from &quot;./Button&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The correct setup depends on whether you are using default or named exports.&lt;/p&gt;
&lt;p&gt;The important part is to check the whole import chain, not just the first file.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;default-exports-and-named-exports-are-a-different-problem-but-still-worth-checking&quot;&gt;Default exports and named exports are a different problem, but still worth checking&lt;/h3&gt;
&lt;p&gt;This usually causes a different error, but it can easily appear around the same time.&lt;/p&gt;
&lt;p&gt;For example, this is a default export:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export default Header;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it is imported like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A named export looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export const Header = () =&amp;gt; {
  return null;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it should be imported like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { Header } from &quot;./Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have already fixed the module resolution error but the app still fails afterward, this is one of the next things I would check.&lt;/p&gt;
&lt;p&gt;Sometimes you fix one problem and Metro finally gets far enough to show you the next one.&lt;/p&gt;
&lt;p&gt;That is just part of debugging.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;monorepos-can-make-module-resolution-more-complicated&quot;&gt;Monorepos can make module resolution more complicated&lt;/h3&gt;
&lt;p&gt;If you are using a monorepo, workspaces, shared packages, or code outside the main app folder, Metro module resolution can become more complicated.&lt;/p&gt;
&lt;p&gt;Your project might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-project/
├── apps/
│   └── mobile/
└── packages/
    └── shared/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the React Native or Expo app might need to import code from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;packages/shared
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In these cases, a normal relative import may not be enough. Metro may need additional configuration to watch or resolve files outside the main project directory.&lt;/p&gt;
&lt;p&gt;If you only started seeing the error after introducing a workspace or shared package, I would focus on your Metro configuration instead of repeatedly reinstalling dependencies.&lt;/p&gt;
&lt;p&gt;Monorepo issues usually need a more specific fix because the correct setup depends heavily on how the project is structured.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;if-you-use-expo-router-check-your-imports-and-project-structure&quot;&gt;If you use Expo Router, check your imports and project structure&lt;/h3&gt;
&lt;p&gt;Expo Router adds another layer to how files are organized.&lt;/p&gt;
&lt;p&gt;For example, your routes may live inside:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you may have layouts, route groups, and nested folders.&lt;/p&gt;
&lt;p&gt;If you recently moved or renamed route files, double-check your imports and references.&lt;/p&gt;
&lt;p&gt;Also make sure the required Expo Router packages are correctly installed for your current Expo setup.&lt;/p&gt;
&lt;p&gt;When something goes wrong inside a routing setup, the error message may make it look like a general Metro problem when the real issue is simply an incorrect import somewhere inside a route or layout.&lt;/p&gt;
&lt;p&gt;Again, start with the exact module Metro says it cannot find.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;do-not-immediately-delete-everything&quot;&gt;Do not immediately delete everything&lt;/h3&gt;
&lt;p&gt;When developers hit a module resolution error, a common reaction is to do all of this at once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Delete node_modules
Delete package-lock.json
Clear Metro
Clear npm cache
Reinstall everything
Restart the computer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes that works.&lt;/p&gt;
&lt;p&gt;But it can also make debugging harder because now you have changed five different things and do not know what actually fixed the problem.&lt;/p&gt;
&lt;p&gt;I would usually check the error in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the exact missing module.&lt;/li&gt;
&lt;li&gt;Check whether the file or package exists.&lt;/li&gt;
&lt;li&gt;Check the spelling and capitalization.&lt;/li&gt;
&lt;li&gt;Check the import path.&lt;/li&gt;
&lt;li&gt;Check whether the dependency is installed.&lt;/li&gt;
&lt;li&gt;Clear the Metro cache.&lt;/li&gt;
&lt;li&gt;Reinstall dependencies if necessary.&lt;/li&gt;
&lt;li&gt;Check Expo or React Native version compatibility.&lt;/li&gt;
&lt;li&gt;Investigate Metro configuration for more complex project setups.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is usually much faster than throwing random commands at the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-quick-example&quot;&gt;A quick example&lt;/h3&gt;
&lt;p&gt;Imagine you get this error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ./components/ProfileCard from App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You check your project and see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;components/
└── Profilecard.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is the capitalization.&lt;/p&gt;
&lt;p&gt;Your import says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import ProfileCard from &quot;./components/ProfileCard&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the actual file is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Profilecard.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rename the file so it matches:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ProfileCard.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then restart Metro if necessary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a simple example, but it shows why reading the exact error matters.&lt;/p&gt;
&lt;p&gt;The error is not always asking you to reinstall your entire project. Sometimes Metro is simply telling you that it cannot find something because the path does not exactly match what exists on disk.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-4&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;&quot;Unable to resolve module&quot; errors can look more serious than they really are.&lt;/p&gt;
&lt;p&gt;Most of the time, the problem comes down to one of these things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A wrong import path&lt;/li&gt;
&lt;li&gt;A missing package&lt;/li&gt;
&lt;li&gt;A typo in the package name&lt;/li&gt;
&lt;li&gt;A filename capitalization problem&lt;/li&gt;
&lt;li&gt;A stale Metro cache&lt;/li&gt;
&lt;li&gt;Broken or missing dependencies&lt;/li&gt;
&lt;li&gt;A file that was moved or renamed&lt;/li&gt;
&lt;li&gt;A more complex Metro configuration issue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The biggest thing I have learned is to &lt;strong&gt;start with the exact module Metro says it cannot find&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Do not start by reinstalling the entire project.&lt;/p&gt;
&lt;p&gt;Check what Metro is looking for, where it is looking from, and whether that file or package actually exists. In many cases, the answer is much simpler than the size of the error message makes it seem.&lt;/p&gt;
&lt;p&gt;If your error is much messier than this—especially if the missing module is buried inside a large Expo, Metro, dependency, or build error—you can also paste the full error into &lt;strong&gt;FixMyError&lt;/strong&gt; and get help breaking down what is actually causing it.&lt;/p&gt;
&lt;p&gt;Sometimes the hard part is not finding a fix. It is figuring out which part of a long error log actually matters.&lt;/p&gt;
&lt;p&gt;Try it here: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;https://www.fixmyerrorapp.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/25620/how-to-fix-unable-to-resolve-module-errors-in-react-native-and-expo</guid>
<pubDate>Fri, 28 Aug 2026 13:36:15 +0000</pubDate>
</item>
<item>
<title>Common CSS Mistakes Beginners Make (and How to Fix Them)</title>
<link>https://coderlegion.com/23008/common-css-mistakes-beginners-make-and-how-to-fix-them</link>
<description>&lt;h2 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Every one of these mistakes cost me real debugging time when I was learning. Not because CSS is inherently confusing — but because nobody explained why these things happen, just that they do. Understanding the &quot;why&quot; is what actually prevents you from repeating them.&lt;/p&gt;
&lt;h2 id=&quot;1-the-box-model-confusion&quot;&gt;1. The Box Model Confusion&lt;/h2&gt;
&lt;p&gt;By default, &lt;strong&gt;width&lt;/strong&gt; and &lt;strong&gt;height&lt;/strong&gt; don't include padding and border — meaning a box you set to &lt;strong&gt;300px&lt;/strong&gt; wide can visually render wider once padding is added.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.box {
  width: 300px;
  padding: 20px;
  border: 2px solid black;
  /* Actual rendered width: 300 + 20+20 + 2+2 = 344px */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;: set this once, globally, at the start of every project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* {
  box-sizing: border-box;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;strong&gt;width: 300px&lt;/strong&gt; means the element is always 300px total, padding and border included. This single line prevents a huge number of layout bugs.&lt;/p&gt;
&lt;h2 id=&quot;2-margin-collapse&quot;&gt;2. Margin Collapse&lt;/h2&gt;
&lt;p&gt;Two vertical margins between sibling elements don't add together — they collapse into the larger of the two.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.first { margin-bottom: 20px; }
.second { margin-top: 30px; }
/* Actual gap between them: 30px, NOT 50px */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confuses beginners constantly because it seems inconsistent — but it's expected browser behavior for vertical margins specifically (doesn't apply to horizontal margins, or to Flexbox/Grid &lt;code&gt;gap&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The fix: use &lt;code&gt;gap&lt;/code&gt; on a flex or grid container instead of relying on margins between children — it doesn't collapse and is far more predictable.&lt;/p&gt;
&lt;h2 id=&quot;3-overusing-important&quot;&gt;3. Overusing &lt;code&gt;!important&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;.button {
  background: blue !important;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;!important&lt;/code&gt; overrides normal CSS specificity rules — which feels like a quick fix, but it creates a mess later. Once you have several &lt;code&gt;!important&lt;/code&gt; rules fighting each other, debugging becomes genuinely painful because normal specificity logic no longer applies.&lt;/p&gt;
&lt;p&gt;The fix: understand specificity instead. IDs beat classes, classes beat elements. If you're reaching for &lt;code&gt;!important&lt;/code&gt;, it usually means a more specific selector (or restructuring your CSS) would solve it more cleanly.&lt;/p&gt;
&lt;h2 id=&quot;4-not-understanding-position-absolute&quot;&gt;4. Not Understanding &lt;code&gt;position: absolute&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;.tooltip {
  position: absolute;
  top: 0;
  left: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beginners often expect this to position relative to the whole page. In reality, &lt;code&gt;position: absolute&lt;/code&gt; positions relative to the nearest ancestor with &lt;code&gt;position: relative&lt;/code&gt; (or &lt;code&gt;absolute/fixed&lt;/code&gt;) set. If no ancestor has that, it &lt;code&gt;positions relative&lt;/code&gt; to the whole document — which is rarely what you actually want.&lt;/p&gt;
&lt;p&gt;The fix: always set &lt;code&gt;position: relative&lt;/code&gt; on the parent container first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.card {
  position: relative;
}
.badge {
  position: absolute;
  top: 8px;
  right: 8px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;5-fighting-flexbox-grid-instead-of-reading-the-docs&quot;&gt;5. Fighting Flexbox/Grid Instead of Reading the Docs&lt;/h2&gt;
&lt;p&gt;A very common beginner pattern: not getting the exact spacing/alignment you want, then adding random margins and padding until it &quot;looks right&quot; by trial and error.&lt;/p&gt;
&lt;p&gt;The fix: learn &lt;code&gt;justify-content, align-items,&lt;/code&gt; and &lt;code&gt;gap&lt;/code&gt; properly (covered fully in Article 2). Once you understand these three properties, most alignment problems have a direct, one-line solution instead of a pile of guessed margins.&lt;/p&gt;
&lt;h2 id=&quot;6-not-resetting-default-browser-styles&quot;&gt;6. Not Resetting Default Browser Styles&lt;/h2&gt;
&lt;p&gt;Browsers apply their own default styles to elements (&lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; gets bullet points and padding, &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; gets specific margins, etc.), and these defaults are inconsistent across browsers.&lt;/p&gt;
&lt;p&gt;The fix: use a simple CSS reset at the top of your stylesheet, or use Tailwind CSS, which includes a solid reset (Preflight) automatically.&lt;/p&gt;
&lt;h2 id=&quot;7-specificity-wars-from-nested-selectors&quot;&gt;7. Specificity Wars from Nested Selectors&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;.card .header .title span {
  color: red;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deeply nested selectors like this become extremely hard to override later, since they carry heavy specificity.&lt;/p&gt;
&lt;p&gt;The fix: keep selectors as flat as possible. A single well-named class (&lt;code&gt;.card-title&lt;/code&gt;) is easier to maintain than four levels of nesting.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts-5&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Almost every CSS mistake on this list comes down to one thing: not understanding why the browser is doing what it's doing, and reaching for a quick patch instead. Once you understand the box model, specificity, and positioning contexts properly, CSS debugging goes from guesswork to genuinely logical problem-solving.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Muhammad Farhan&lt;/strong&gt; is a Frontend Developer specializing in React.js and Tailwind CSS, based in Dera Ismail Khan, Pakistan.&lt;br&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://muhammadfarhandev.netlify.app&quot;&gt;Muhammad Farhan&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23008/common-css-mistakes-beginners-make-and-how-to-fix-them</guid>
<pubDate>Tue, 25 Aug 2026 05:00:00 +0000</pubDate>
</item>
<item>
<title>Systems Thinking Beats Component Thinking, A Lesson From Human Anatomy</title>
<link>https://coderlegion.com/25316/systems-thinking-beats-component-thinking-a-lesson-from-human-anatomy</link>
<description>&lt;p&gt;Most of us here debug systems for a living, and the best mental model I have found for cascading failure did not come out of a distributed systems paper. It came out of an anatomy guide, so bear with me for a minute.&lt;/p&gt;
&lt;p&gt;The human body is usually taught as eleven organ systems, each with its own list of organs and its own list of functions. Students memorize the lists, pass the exam, and forget most of it, because a list is not a model. The version that sticks treats the eleven as one machine with eleven subsystems and a shared objective, which happens to be exactly how we are supposed to think about the services we run.&lt;/p&gt;
&lt;h3 id=&quot;eleven-subsystems-one-shared-objective&quot;&gt;Eleven Subsystems, One Shared Objective&lt;/h3&gt;
&lt;p&gt;Every organ system exists to serve one goal: homeostasis, holding internal conditions inside a narrow band while the outside world keeps changing. Core temperature near 37 degrees Celsius. Blood pH between 7.35 and 7.45. Fasting blood glucose between 70 and 100 milligrams per deciliter.&lt;/p&gt;
&lt;p&gt;Nothing in that architecture is decorative. Each subsystem has one job, and the job is defined relative to the shared objective rather than in isolation. That is the part that transfers. When a service in your stack cannot state its purpose in terms of the system level objective, it usually means nobody has decided what the objective is.&lt;/p&gt;
&lt;h3 id=&quot;failure-travels-along-the-couplings&quot;&gt;Failure Travels Along The Couplings&lt;/h3&gt;
&lt;p&gt;Kidney failure causes fluid retention, which strains the heart. Diabetes is an endocrine problem that goes on to damage blood vessels, nerves, kidneys and eyes. Chronic liver disease impairs clotting factor production, which shows up in the blood and eventually in the brain.&lt;/p&gt;
&lt;p&gt;None of that is surprising once you have the dependency graph. It is very surprising when all you have is eleven separate lists. This is the same reason a connection pool exhaustion incident gets filed as a checkout outage: the symptom appears where the coupling ends, not where the fault started. Medicine moved toward a systems level approach for exactly this reason, and it is worth noticing that the field with the highest stakes got there first.&lt;/p&gt;
&lt;h3 id=&quot;the-control-loop-is-already-familiar&quot;&gt;The Control Loop Is Already Familiar&lt;/h3&gt;
&lt;p&gt;Homeostasis runs on negative feedback. A sensor detects a deviation from a set point, a control center evaluates it, an effector corrects it. Blood glucose rises after a meal, the pancreas releases insulin, cells absorb glucose, levels come back down. Drop too low and glucagon does the reverse, telling the liver to put stored glucose back into circulation.&lt;/p&gt;
&lt;p&gt;That is a control loop with a set point, an error signal and an actuator, which is to say it is your autoscaler. Positive feedback exists in the body too and it is rare, because it does not stabilize anything, it drives a process to completion. Blood clotting is the example: activated clotting factors activate more clotting factors until the wound is sealed. When that pattern shows up in software with no terminating condition, we call it a retry storm.&lt;/p&gt;
&lt;h3 id=&quot;what-to-take-from-it&quot;&gt;What To Take From It&lt;/h3&gt;
&lt;p&gt;Pick one event and trace it through the whole system instead of studying components one at a time. Climb a flight of stairs and six subsystems respond in the same second: muscles pull more glucose, the nervous system speeds the heart, breathing deepens, the adrenals release epinephrine, sweat glands open for thermoregulation, the kidneys hold fluid to protect blood pressure. One event, six subsystems, one objective.&lt;/p&gt;
&lt;p&gt;If you want the biology properly, all eleven systems with their organs, functions and interactions laid out in one place, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.learnhowtoscience.com/human-body-systems/&quot;&gt;this guide covers it&lt;/a&gt; and it is where the examples above came from.&lt;/p&gt;
&lt;p&gt;Do that same trace with an incident timeline and you will learn more in an afternoon than a month of reading service docs in isolation will teach you. The lists were never the point. The couplings were.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25316/systems-thinking-beats-component-thinking-a-lesson-from-human-anatomy</guid>
<pubDate>Mon, 24 Aug 2026 22:15:13 +0000</pubDate>
</item>
<item>
<title>The Best Developers Don’t Fix Bugs, They Fix the Reason Bugs Exist</title>
<link>https://coderlegion.com/24761/the-best-developers-dont-fix-bugs-they-fix-the-reason-bugs-exist</link>
<description>&lt;p&gt;A bug is rarely just a bug.&lt;/p&gt;
&lt;p&gt;Sometimes it is a missing validation.&lt;br&gt;
Sometimes it is unclear requirements.&lt;br&gt;
Sometimes it is technical debt.&lt;br&gt;
And sometimes, it is simply a developer making an assumption that nobody questioned.&lt;/p&gt;
&lt;p&gt;The easy response is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Let’s fix it.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The better question is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“Why was this bug possible in the first place?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;fixing-the-symptom-vs-fixing-the-system&quot;&gt;Fixing the symptom vs. fixing the system&lt;/h4&gt;
&lt;p&gt;Imagine a production API crashes because it receives an unexpected value.&lt;/p&gt;
&lt;p&gt;A quick fix might be adding one &lt;code&gt;if&lt;/code&gt; statement.&lt;/p&gt;
&lt;p&gt;Problem solved?&lt;/p&gt;
&lt;p&gt;Maybe.&lt;/p&gt;
&lt;p&gt;But what if the same invalid data can enter through three other endpoints?&lt;/p&gt;
&lt;p&gt;Now you fixed the symptom, not the problem.&lt;/p&gt;
&lt;p&gt;Good developers look deeper:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where did the bad data come from?&lt;/li&gt;
&lt;li&gt;Why wasn't it validated earlier?&lt;/li&gt;
&lt;li&gt;Why didn't the tests catch it?&lt;/li&gt;
&lt;li&gt;Could the system make the same mistake again?&lt;/li&gt;
&lt;li&gt;What can we change so this entire class of bugs becomes harder to create?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;every-bug-contains-information&quot;&gt;Every bug contains information&lt;/h4&gt;
&lt;p&gt;Instead of seeing bugs only as failures, treat them as &lt;strong&gt;feedback from your system&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A recurring bug might be telling you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your requirements are unclear.&lt;/li&gt;
&lt;li&gt;Your tests are too narrow.&lt;/li&gt;
&lt;li&gt;Your architecture has a weak boundary.&lt;/li&gt;
&lt;li&gt;Your monitoring is insufficient.&lt;/li&gt;
&lt;li&gt;Your team is repeating the same assumption.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The bug is simply the messenger.&lt;/p&gt;
&lt;h4 id=&quot;a-simple-debugging-habit&quot;&gt;A simple debugging habit&lt;/h4&gt;
&lt;p&gt;The next time you fix a production issue, spend 5 extra minutes asking:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. What happened?&lt;/strong&gt;&lt;br&gt;
Understand the actual failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Why did it happen?&lt;/strong&gt;&lt;br&gt;
Find the root cause, not just the visible error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Why wasn't it caught earlier?&lt;/strong&gt;&lt;br&gt;
Look at tests, reviews, monitoring, and processes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. How can we prevent the same category of failure?&lt;/strong&gt;&lt;br&gt;
Improve the system, not just the line of code.&lt;/p&gt;
&lt;h4 id=&quot;-the-real-goal-of-debugging&quot;&gt;🚀 The real goal of debugging&lt;/h4&gt;
&lt;p&gt;The goal isn't to have &lt;strong&gt;zero bugs&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That's unrealistic.&lt;/p&gt;
&lt;p&gt;The goal is to build systems where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bugs are detected earlier,&lt;/li&gt;
&lt;li&gt;failures are easier to understand,&lt;/li&gt;
&lt;li&gt;mistakes are harder to repeat,&lt;/li&gt;
&lt;li&gt;and developers learn something from every incident.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Great developers don't just make bugs disappear. They make the next bug less likely.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What do you usually do after fixing a production bug—move on immediately, or investigate why it happened?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sumita&lt;br&gt;
Web Developer&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24761/the-best-developers-dont-fix-bugs-they-fix-the-reason-bugs-exist</guid>
<pubDate>Mon, 17 Aug 2026 12:14:40 +0000</pubDate>
</item>
<item>
<title>Your Code Works. But Can You Explain Why?</title>
<link>https://coderlegion.com/24692/your-code-works-but-can-you-explain-why</link>
<description>&lt;p&gt;There’s a dangerous stage in every developer’s career:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When the code works, but you don’t know why.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You change three things.&lt;br&gt;
Restart the server.&lt;br&gt;
Clear the cache.&lt;br&gt;
Run it again.&lt;/p&gt;
&lt;p&gt;Suddenly—&lt;strong&gt;it works.&lt;/strong&gt; 🎉&lt;/p&gt;
&lt;p&gt;But you haven’t actually fixed the problem.&lt;/p&gt;
&lt;p&gt;You’ve only lost the evidence.&lt;/p&gt;
&lt;h3 id=&quot;the-real-skill-isn-t-fixing-bugs&quot;&gt;The Real Skill Isn’t Fixing Bugs&lt;/h3&gt;
&lt;p&gt;Good developers don’t just make errors disappear.&lt;/p&gt;
&lt;p&gt;They ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What exactly failed?&lt;/li&gt;
&lt;li&gt;Why did it fail?&lt;/li&gt;
&lt;li&gt;What changed?&lt;/li&gt;
&lt;li&gt;What evidence proves the fix?&lt;/li&gt;
&lt;li&gt;Could the same bug happen somewhere else?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This mindset turns debugging from &lt;strong&gt;guessing into investigation&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;stop-changing-five-things-at-once&quot;&gt;Stop Changing Five Things at Once&lt;/h3&gt;
&lt;p&gt;Imagine a bug appears in production.&lt;/p&gt;
&lt;p&gt;You change the database query, update a dependency, modify the API request, restart the service, and change a configuration value.&lt;/p&gt;
&lt;p&gt;The problem disappears.&lt;/p&gt;
&lt;p&gt;Now what do you know?&lt;/p&gt;
&lt;p&gt;Almost nothing.&lt;/p&gt;
&lt;p&gt;You don't know which change fixed it.&lt;/p&gt;
&lt;p&gt;Instead:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change one thing. Test. Observe. Learn.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It may feel slower, but it creates knowledge.&lt;/p&gt;
&lt;h3 id=&quot;errors-are-information&quot;&gt;Errors Are Information&lt;/h3&gt;
&lt;p&gt;An error message isn't your enemy.&lt;/p&gt;
&lt;p&gt;A stack trace isn't noise.&lt;/p&gt;
&lt;p&gt;A failed test isn't wasted time.&lt;/p&gt;
&lt;p&gt;They are clues.&lt;/p&gt;
&lt;p&gt;The better you become at reading those clues, the less you depend on trial and error.&lt;/p&gt;
&lt;h3 id=&quot;the-best-developers-build-mental-models&quot;&gt;The Best Developers Build Mental Models&lt;/h3&gt;
&lt;p&gt;A senior developer doesn't necessarily know every solution.&lt;/p&gt;
&lt;p&gt;They know &lt;strong&gt;how to find the solution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When something breaks, they build a hypothesis:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“I think the problem is happening because X.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then they test it.&lt;/p&gt;
&lt;p&gt;If they're wrong, that's useful too.&lt;/p&gt;
&lt;p&gt;Now they know where the problem &lt;strong&gt;isn't&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;one-simple-rule&quot;&gt;One Simple Rule&lt;/h3&gt;
&lt;p&gt;Before changing your code, ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“What do I expect this change to prove?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you can't answer that, you're probably guessing.&lt;/p&gt;
&lt;p&gt;Debugging isn't about making the error disappear as quickly as possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's about learning why the error existed in the first place.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What’s one debugging habit that made you a better developer?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sumita &lt;br&gt;
Web Developer&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24692/your-code-works-but-can-you-explain-why</guid>
<pubDate>Sat, 15 Aug 2026 18:48:06 +0000</pubDate>
</item>
<item>
<title>The Code Worked Yesterday. What Changed?</title>
<link>https://coderlegion.com/24621/the-code-worked-yesterday-what-changed</link>
<description>&lt;p&gt;One of the most frustrating moments in development is when &lt;strong&gt;code that worked perfectly yesterday suddenly stops working today.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You didn’t change the feature.&lt;/p&gt;
&lt;p&gt;You didn’t intentionally modify the logic.&lt;/p&gt;
&lt;p&gt;Yet suddenly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The API returns an error.&lt;/li&gt;
&lt;li&gt;The build fails.&lt;/li&gt;
&lt;li&gt;A dependency breaks.&lt;/li&gt;
&lt;li&gt;Tests start failing.&lt;/li&gt;
&lt;li&gt;Production behaves differently from development.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So what happened?&lt;/p&gt;
&lt;h3 id=&quot;the-hidden-problem-software-doesn-t-live-alone&quot;&gt;The Hidden Problem: Software Doesn't Live Alone&lt;/h3&gt;
&lt;p&gt;We often think about our code as if it exists in isolation.&lt;/p&gt;
&lt;p&gt;It doesn't.&lt;/p&gt;
&lt;p&gt;Our applications depend on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Libraries and frameworks&lt;/li&gt;
&lt;li&gt;Operating systems&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Cloud services&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Third-party packages&lt;/li&gt;
&lt;li&gt;Build tools&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes &lt;strong&gt;your code didn't break. Its environment changed.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-developer-trap&quot;&gt;The Developer Trap&lt;/h3&gt;
&lt;p&gt;When something breaks, the first instinct is often:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;What did I do wrong?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's not always the best question.&lt;/p&gt;
&lt;p&gt;A better question is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;What changed?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Check the timeline.&lt;/p&gt;
&lt;p&gt;What changed between the last known working state and the current failure?&lt;/p&gt;
&lt;p&gt;Maybe:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A package was automatically updated.&lt;/li&gt;
&lt;li&gt;An API changed its response.&lt;/li&gt;
&lt;li&gt;A server configuration changed.&lt;/li&gt;
&lt;li&gt;A certificate expired.&lt;/li&gt;
&lt;li&gt;A database migration ran.&lt;/li&gt;
&lt;li&gt;An environment variable disappeared.&lt;/li&gt;
&lt;li&gt;A dependency introduced a breaking change.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-simple-debugging-habit-1&quot;&gt;A Simple Debugging Habit&lt;/h3&gt;
&lt;p&gt;Before changing code, compare the working and broken environments.&lt;/p&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. What changed?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. When did it change?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;3. Who or what changed it?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;4. Can I reproduce the failure?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;5. Can I roll back the change?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This can save hours of random debugging.&lt;/p&gt;
&lt;h3 id=&quot;the-bigger-lesson&quot;&gt;The Bigger Lesson&lt;/h3&gt;
&lt;p&gt;Good developers don't just write code that works.&lt;/p&gt;
&lt;p&gt;They build systems that make &lt;strong&gt;change visible&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That's why version locking, meaningful logs, monitoring, automated tests, CI/CD, dependency management, and configuration tracking matter.&lt;/p&gt;
&lt;p&gt;The goal isn't to prevent every change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal is to know what changed when something breaks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because debugging isn't always about finding the broken line.&lt;/p&gt;
&lt;p&gt;Sometimes it's about finding the &lt;strong&gt;missing difference between yesterday and today.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;what-about-you-&quot;&gt;What about you?&lt;/h4&gt;
&lt;p&gt;What's the strangest time your code suddenly stopped working even though you hadn't changed it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Share the story — the weird ones usually teach developers the most.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24621/the-code-worked-yesterday-what-changed</guid>
<pubDate>Fri, 14 Aug 2026 09:07:19 +0000</pubDate>
</item>
<item>
<title>The Best Developers Don’t Know Everything They Know How to Find Out</title>
<link>https://coderlegion.com/24615/the-best-developers-dont-know-everything-they-know-how-to-find-out</link>
<description>&lt;p&gt;There’s a common misconception in software development:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Senior developers know the answers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They don’t.&lt;/p&gt;
&lt;p&gt;They simply know &lt;strong&gt;how to find the right answer without wasting hours.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A developer can’t memorize every API, framework, error message, database behavior, or cloud configuration. Technology changes too quickly.&lt;/p&gt;
&lt;p&gt;What separates a strong developer from a struggling one is often the ability to investigate.&lt;/p&gt;
&lt;h4 id=&quot;-1-read-the-error-before-searching&quot;&gt;🔍 1. Read the Error Before Searching&lt;/h4&gt;
&lt;p&gt;A common habit is to immediately copy an error into Google or ChatGPT.&lt;/p&gt;
&lt;p&gt;Instead, ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What exactly failed?&lt;/li&gt;
&lt;li&gt;Which file and line caused it?&lt;/li&gt;
&lt;li&gt;What changed recently?&lt;/li&gt;
&lt;li&gt;Is this an application problem or an environment problem?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The error message often contains more information than we think.&lt;/p&gt;
&lt;h4 id=&quot;-2-build-a-hypothesis&quot;&gt;🧠 2. Build a Hypothesis&lt;/h4&gt;
&lt;p&gt;Don't randomly change five things and hope the bug disappears.&lt;/p&gt;
&lt;p&gt;Make a small hypothesis:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;I think this API is returning null because the authentication token isn't being passed.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then test it.&lt;/p&gt;
&lt;p&gt;If you're wrong, that's useful information too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging isn't guessing. It's reducing uncertainty.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;-3-learn-to-read-documentation&quot;&gt;📚 3. Learn to Read Documentation&lt;/h4&gt;
&lt;p&gt;Knowing how to search documentation is an underrated engineering skill.&lt;/p&gt;
&lt;p&gt;Instead of searching:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;How do I make this work?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Search for something specific:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;React useEffect cleanup behavior&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;PostgreSQL index query planner&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Docker container environment variables&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Better questions produce better answers.&lt;/p&gt;
&lt;h4 id=&quot;-4-use-ai-as-an-investigator-not-an-oracle&quot;&gt;🤖 4. Use AI as an Investigator, Not an Oracle&lt;/h4&gt;
&lt;p&gt;AI can explain errors, suggest approaches, generate examples, and help you explore unfamiliar code.&lt;/p&gt;
&lt;p&gt;But blindly accepting its answer creates another problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You still need to verify the result.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ask AI:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;What assumptions are you making?&quot;&lt;/li&gt;
&lt;li&gt;&quot;How can I test this?&quot;&lt;/li&gt;
&lt;li&gt;&quot;What could make this solution fail?&quot;&lt;/li&gt;
&lt;li&gt;&quot;Show me a simpler alternative.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal isn't to avoid thinking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal is to think faster and better.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;-5-keep-your-own-debugging-knowledge&quot;&gt;🛠️ 5. Keep Your Own Debugging Knowledge&lt;/h4&gt;
&lt;p&gt;Every difficult bug teaches you something.&lt;/p&gt;
&lt;p&gt;Write down the important ones.&lt;/p&gt;
&lt;p&gt;Not a huge documentation system.&lt;/p&gt;
&lt;p&gt;Just short notes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem → Investigation → Root Cause → Fix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After six months, you'll have something incredibly valuable:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your own engineering playbook.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-real-developer-skill&quot;&gt;The Real Developer Skill&lt;/h4&gt;
&lt;p&gt;Frameworks will change.&lt;/p&gt;
&lt;p&gt;Programming languages will evolve.&lt;/p&gt;
&lt;p&gt;AI tools will improve.&lt;/p&gt;
&lt;p&gt;But one skill will remain valuable:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The ability to turn &quot;I don't know&quot; into &quot;I can figure this out.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's what makes a developer adaptable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What's one debugging or problem-solving technique you've learned that you now use almost every day?&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24615/the-best-developers-dont-know-everything-they-know-how-to-find-out</guid>
<pubDate>Fri, 14 Aug 2026 03:22:23 +0000</pubDate>
</item>
<item>
<title>The 10-Minute Rule: A Simple Way to Debug Software Faster</title>
<link>https://coderlegion.com/24482/the-10-minute-rule-a-simple-way-to-debug-software-faster</link>
<description>&lt;p&gt;A bug appears in production.&lt;/p&gt;
&lt;p&gt;Your first instinct is often to start changing code immediately.&lt;/p&gt;
&lt;p&gt;Add a condition.&lt;br&gt;
Change a query.&lt;br&gt;
Restart the service.&lt;br&gt;
Update a dependency.&lt;br&gt;
Try something else.&lt;/p&gt;
&lt;p&gt;Sometimes it works. But you may have no idea &lt;strong&gt;why&lt;/strong&gt; it worked.&lt;/p&gt;
&lt;p&gt;That is where debugging becomes dangerous.&lt;/p&gt;
&lt;h3 id=&quot;the-10-minute-rule&quot;&gt;The 10-Minute Rule&lt;/h3&gt;
&lt;p&gt;Before changing the code, spend the first &lt;strong&gt;10 minutes understanding the problem&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What exactly is failing?&lt;/li&gt;
&lt;li&gt;When did it start?&lt;/li&gt;
&lt;li&gt;Can I reproduce it?&lt;/li&gt;
&lt;li&gt;What changed recently?&lt;/li&gt;
&lt;li&gt;Is the problem in the frontend, backend, database, network, or deployment?&lt;/li&gt;
&lt;li&gt;What does the error message actually tell me?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This small habit can save hours.&lt;/p&gt;
&lt;h3 id=&quot;stop-guessing-start-with-evidence-&quot;&gt;Stop Guessing. Start With Evidence.&lt;/h3&gt;
&lt;p&gt;Imagine an API suddenly returns &lt;code&gt;500&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A junior approach might be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;The database must be broken.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A better approach is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check the application logs.&lt;/li&gt;
&lt;li&gt;Identify the failing endpoint.&lt;/li&gt;
&lt;li&gt;Check the request parameters.&lt;/li&gt;
&lt;li&gt;Check database connectivity.&lt;/li&gt;
&lt;li&gt;Look at the stack trace.&lt;/li&gt;
&lt;li&gt;Reproduce the request locally.&lt;/li&gt;
&lt;li&gt;Form a hypothesis.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you're debugging based on evidence instead of assumptions.&lt;/p&gt;
&lt;h3 id=&quot;don-t-change-five-things-at-once&quot;&gt;Don't Change Five Things at Once&lt;/h3&gt;
&lt;p&gt;One of the biggest debugging mistakes is making multiple changes simultaneously.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update the dependency&lt;/li&gt;
&lt;li&gt;Change the SQL query&lt;/li&gt;
&lt;li&gt;Modify the API&lt;/li&gt;
&lt;li&gt;Restart Docker&lt;/li&gt;
&lt;li&gt;Clear the cache&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then the problem disappears.&lt;/p&gt;
&lt;p&gt;Great!&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;which change fixed it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You don't know.&lt;/p&gt;
&lt;p&gt;Instead, make one controlled change at a time whenever possible.&lt;/p&gt;
&lt;p&gt;That gives you something much more valuable than a quick fix:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;read-error-messages-carefully&quot;&gt;Read Error Messages Carefully&lt;/h3&gt;
&lt;p&gt;Error messages are often treated as annoying obstacles.&lt;/p&gt;
&lt;p&gt;They shouldn't be.&lt;/p&gt;
&lt;p&gt;A good error message can tell you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where the failure happened&lt;/li&gt;
&lt;li&gt;What operation failed&lt;/li&gt;
&lt;li&gt;Which value caused the problem&lt;/li&gt;
&lt;li&gt;Which component reported the error&lt;/li&gt;
&lt;li&gt;Sometimes even what should be fixed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't immediately search the entire error message online.&lt;/p&gt;
&lt;p&gt;First, understand what it is telling you.&lt;/p&gt;
&lt;h3 id=&quot;the-real-skill-is-building-a-mental-model&quot;&gt;The Real Skill Is Building a Mental Model&lt;/h3&gt;
&lt;p&gt;Great debugging isn't about memorizing every error.&lt;/p&gt;
&lt;p&gt;It's about understanding how your system works.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User → Browser → API → Service → Database → External API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When something breaks, ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;At which point did the expected behavior change?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That question can dramatically reduce the search area.&lt;/p&gt;
&lt;h3 id=&quot;debugging-is-also-communication&quot;&gt;Debugging Is Also Communication&lt;/h3&gt;
&lt;p&gt;In a team, saying:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;The API is broken.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;isn't very useful.&lt;/p&gt;
&lt;p&gt;Instead:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;The &lt;code&gt;/orders&lt;/code&gt; endpoint started returning 500 errors after yesterday's deployment. The request reaches the service successfully, but the database query fails when &lt;code&gt;customer_id&lt;/code&gt; is null.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now another developer can immediately help.&lt;/p&gt;
&lt;p&gt;Good debugging produces &lt;strong&gt;good information&lt;/strong&gt;, not just fixes.&lt;/p&gt;
&lt;h3 id=&quot;final-thought&quot;&gt;Final Thought&lt;/h3&gt;
&lt;p&gt;The fastest developer isn't always the person who types the fastest.&lt;/p&gt;
&lt;p&gt;Sometimes it's the developer who &lt;strong&gt;stops typing and starts thinking&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Before changing the code, understand the failure.&lt;/p&gt;
&lt;p&gt;Before guessing, collect evidence.&lt;/p&gt;
&lt;p&gt;Before applying a fix, understand the cause.&lt;/p&gt;
&lt;p&gt;Because a bug that disappears is not necessarily a bug that you solved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debug the problem. Don't just silence it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Sumita &lt;br&gt;
 Web Developer&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24482/the-10-minute-rule-a-simple-way-to-debug-software-faster</guid>
<pubDate>Wed, 12 Aug 2026 06:18:38 +0000</pubDate>
</item>
<item>
<title>How to Fix EAS Build Failed Errors in Expo</title>
<link>https://coderlegion.com/24440/how-to-fix-eas-build-failed-errors-in-expo</link>
<description>&lt;p&gt;There is a particular kind of Expo error that I think almost every developer eventually runs into.&lt;/p&gt;
&lt;p&gt;You make a change to your project, run your usual build command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and instead of getting a nice &lt;code&gt;.apk&lt;/code&gt; or &lt;code&gt;.aab&lt;/code&gt; at the end, you get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it.&lt;/p&gt;
&lt;p&gt;Well, not really.&lt;/p&gt;
&lt;p&gt;If you open the build logs, you get several hundred lines of output containing Gradle tasks, Android dependencies, warnings, stack traces, environment information, and somewhere in the middle of all of that is the actual reason your build failed.&lt;/p&gt;
&lt;p&gt;I've been there more times than I'd like to admit.&lt;/p&gt;
&lt;p&gt;The frustrating part is that &lt;strong&gt;&quot;EAS Build failed&quot; isn't really an error by itself&lt;/strong&gt;. EAS is just telling you that something went wrong during the build. The actual problem can be a completely different issue.&lt;/p&gt;
&lt;p&gt;It might be an Android dependency conflict.&lt;/p&gt;
&lt;p&gt;It might be a missing environment variable.&lt;/p&gt;
&lt;p&gt;It might be an invalid &lt;code&gt;app.json&lt;/code&gt; configuration.&lt;/p&gt;
&lt;p&gt;It might be a native package that doesn't work with your current Expo SDK.&lt;/p&gt;
&lt;p&gt;Or it might be something as simple as a credentials problem.&lt;/p&gt;
&lt;p&gt;So instead of trying random fixes, let's go through how I approach these failures and how to find the actual cause.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-don-t-focus-on-eas-build-failed-&quot;&gt;First: Don't Focus on &quot;EAS Build Failed&quot;&lt;/h3&gt;
&lt;p&gt;When you see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;it's tempting to search for that exact phrase.&lt;/p&gt;
&lt;p&gt;The problem is that thousands of completely different problems can end with the same message.&lt;/p&gt;
&lt;p&gt;EAS Build is responsible for building your application. It isn't necessarily responsible for the thing that broke.&lt;/p&gt;
&lt;p&gt;For example, your log might eventually contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:mergeDexDebug'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's an Android dependency/build problem.&lt;/p&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Could not resolve all files for configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's pointing toward dependency resolution.&lt;/p&gt;
&lt;p&gt;Or you might see something related to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Missing environment variable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's an environment/configuration problem.&lt;/p&gt;
&lt;p&gt;And if you see something related to signing credentials, you're dealing with an entirely different category of failure.&lt;/p&gt;
&lt;p&gt;The first thing I do is therefore &lt;strong&gt;ignore the generic &quot;Build failed&quot; message and find the first meaningful error underneath it.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-open-the-full-eas-build-logs&quot;&gt;1. Open the Full EAS Build Logs&lt;/h2&gt;
&lt;p&gt;Start by looking at the failed build in the EAS dashboard.&lt;/p&gt;
&lt;p&gt;You can also list recent builds from your terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build:list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the failed build and open its logs.&lt;/p&gt;
&lt;p&gt;Don't just look at the last few lines.&lt;/p&gt;
&lt;p&gt;The last line might say:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but that doesn't tell you much.&lt;/p&gt;
&lt;p&gt;Instead, search through the logs for terms such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ERROR
FAILURE
Error:
Caused by:
Exception
Could not
Unable to
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful error is often somewhere above the final failure message.&lt;/p&gt;
&lt;p&gt;For example, imagine the bottom of your log looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;gt; Task :app:mergeDexRelease FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:mergeDexRelease'.

&amp;gt; com.android.builder.dexing.DexArchiveMergerException:
  Error while merging dex archives

BUILD FAILED

Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful information isn't really:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:mergeDexRelease'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and whatever caused the DEX merge to fail.&lt;/p&gt;
&lt;p&gt;That distinction saves a lot of time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-check-whether-the-problem-is-actually-your-expo-configuration&quot;&gt;2. Check Whether the Problem Is Actually Your Expo Configuration&lt;/h2&gt;
&lt;p&gt;One of the first things I check after an EAS build fails is the project's configuration.&lt;/p&gt;
&lt;p&gt;Depending on your setup, this can include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.json
app.config.js
app.config.ts
eas.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A small configuration mistake can prevent the native project from being built correctly.&lt;/p&gt;
&lt;p&gt;For example, if you've recently changed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;android&quot;: {
    &quot;package&quot;: &quot;com.example.myapp&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or added a plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;plugins&quot;: [
    &quot;some-native-package&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the build immediately started failing, that change is worth investigating.&lt;/p&gt;
&lt;p&gt;The same applies to permissions, native configuration, app identifiers, and Expo config plugins.&lt;/p&gt;
&lt;p&gt;If the failure started immediately after a configuration change, don't assume the EAS service itself is broken.&lt;/p&gt;
&lt;p&gt;Look at what changed in your project first.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-check-your-expo-sdk-and-native-dependencies&quot;&gt;3. Check Your Expo SDK and Native Dependencies&lt;/h2&gt;
&lt;p&gt;This is one of the biggest sources of trouble I've encountered with Expo projects.&lt;/p&gt;
&lt;p&gt;Expo SDK versions are tied to specific versions of React Native and a collection of native dependencies.&lt;/p&gt;
&lt;p&gt;You can have a perfectly valid JavaScript project and still break the native build by installing a package that doesn't fit your current environment.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install some-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;looks harmless.&lt;/p&gt;
&lt;p&gt;But if that package contains native Android or iOS code, it becomes part of your native build.&lt;/p&gt;
&lt;p&gt;That's where things can get interesting.&lt;/p&gt;
&lt;p&gt;Before randomly upgrading packages, check your project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can identify common dependency and configuration problems.&lt;/p&gt;
&lt;p&gt;If you're using Expo, I also recommend using Expo's package installation command when appropriate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install package-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rather than blindly installing a version with npm.&lt;/p&gt;
&lt;p&gt;Expo can select a version that is compatible with your current SDK.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-if-the-error-mentions-gradle-investigate-the-android-build&quot;&gt;4. If the Error Mentions Gradle, Investigate the Android Build&lt;/h2&gt;
&lt;p&gt;Sometimes an EAS failure is actually an Android build failure hiding underneath Expo.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:mergeDexRelease'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Could not resolve all files for configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Duplicate class found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are not really &quot;Expo errors.&quot;&lt;/p&gt;
&lt;p&gt;They're Android/Gradle dependency problems occurring during your Expo build.&lt;/p&gt;
&lt;p&gt;This distinction matters because searching for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EAS Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will give you a huge number of unrelated results.&lt;/p&gt;
&lt;p&gt;Searching for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task mergeDexRelease React Native
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is much more useful.&lt;/p&gt;
&lt;p&gt;That's generally how I approach these logs: &lt;strong&gt;follow the most specific error rather than the generic EAS failure.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-check-your-environment-variables&quot;&gt;5. Check Your Environment Variables&lt;/h2&gt;
&lt;p&gt;Another surprisingly common cause of failed builds is an environment variable that exists locally but doesn't exist in EAS.&lt;/p&gt;
&lt;p&gt;For example, your application might expect:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;API_URL
SUPABASE_URL
SUPABASE_ANON_KEY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your local development environment works because those variables exist in your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Then you create an EAS build and suddenly something breaks.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because the remote build environment doesn't automatically have every variable from your local machine.&lt;/p&gt;
&lt;p&gt;Check your EAS environment configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas env:list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure the variables your application actually needs are available in the environment being used for the build.&lt;/p&gt;
&lt;p&gt;This is especially important if the failure happens only on EAS while:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;works perfectly locally.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-check-your-eas-build-profile&quot;&gt;6. Check Your EAS Build Profile&lt;/h2&gt;
&lt;p&gt;Your &lt;code&gt;eas.json&lt;/code&gt; determines how your builds are configured.&lt;/p&gt;
&lt;p&gt;A typical configuration might look something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;build&quot;: {
    &quot;development&quot;: {
      &quot;developmentClient&quot;: true,
      &quot;distribution&quot;: &quot;internal&quot;
    },
    &quot;preview&quot;: {
      &quot;distribution&quot;: &quot;internal&quot;
    },
    &quot;production&quot;: {}
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the problem only happens with one profile, compare that profile with the one that works.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build --profile preview
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;might work while:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build --profile production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fails.&lt;/p&gt;
&lt;p&gt;That immediately gives you another clue.&lt;/p&gt;
&lt;p&gt;The problem may not be your entire project. It may be something specific to the build profile.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;7-don-t-ignore-credentials-errors&quot;&gt;7. Don't Ignore Credentials Errors&lt;/h2&gt;
&lt;p&gt;If the log mentions things such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;credentials
keystore
provisioning profile
certificate
signing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;don't start changing Gradle dependencies.&lt;/p&gt;
&lt;p&gt;You're probably dealing with app signing or build credentials instead.&lt;/p&gt;
&lt;p&gt;For Android, EAS can manage your app signing credentials for you.&lt;/p&gt;
&lt;p&gt;You can inspect the credentials with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas credentials
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the error is specifically about signing, work through the credential configuration rather than treating it like a code or dependency problem.&lt;/p&gt;
&lt;p&gt;This is one of those situations where identifying the &lt;strong&gt;category of the error&lt;/strong&gt; is much more useful than knowing a particular command.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;8-if-it-worked-before-ask-what-changed&quot;&gt;8. If It Worked Before, Ask What Changed&lt;/h2&gt;
&lt;p&gt;This is probably my favorite debugging question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What changed immediately before the build started failing?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Don't underestimate how useful this is.&lt;/p&gt;
&lt;p&gt;If yesterday you had:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EAS build → SUCCESS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and today you have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EAS build → FAILED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;something changed.&lt;/p&gt;
&lt;p&gt;Maybe you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;installed a package&lt;/li&gt;
&lt;li&gt;upgraded Expo&lt;/li&gt;
&lt;li&gt;changed &lt;code&gt;app.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;changed &lt;code&gt;eas.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;modified environment variables&lt;/li&gt;
&lt;li&gt;changed an Android dependency&lt;/li&gt;
&lt;li&gt;added a config plugin&lt;/li&gt;
&lt;li&gt;changed native code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Start there.&lt;/p&gt;
&lt;p&gt;For example, if the failure appeared immediately after:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install some-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would investigate that package before touching ten unrelated parts of the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;9-clean-up-before-rebuilding&quot;&gt;9. Clean Up Before Rebuilding&lt;/h2&gt;
&lt;p&gt;If you've already made changes trying to fix the problem, make sure you're not debugging an old local state.&lt;/p&gt;
&lt;p&gt;For a native project, a clean build can sometimes help:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
cd ..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Expo projects using prebuild, you may also need to regenerate native projects depending on your setup.&lt;/p&gt;
&lt;p&gt;But don't use cleaning as your first response to every error.&lt;/p&gt;
&lt;p&gt;If the build is failing because a dependency is genuinely incompatible, cleaning won't magically make the dependency compatible.&lt;/p&gt;
&lt;p&gt;It only removes cached/generated build artifacts.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-most-important-part-find-the-root-error&quot;&gt;The Most Important Part: Find the Root Error&lt;/h2&gt;
&lt;p&gt;This is the part that took me the longest to appreciate.&lt;/p&gt;
&lt;p&gt;When a build log contains hundreds of lines, the error at the bottom isn't necessarily the error you need to fix.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Task :app:assembleRelease FAILED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not particularly useful.&lt;/p&gt;
&lt;p&gt;Neither is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful part might be 30, 50, or even 200 lines above it.&lt;/p&gt;
&lt;p&gt;You might eventually find:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Duplicate class androidx.lifecycle.ViewModel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And suddenly the whole situation makes sense.&lt;/p&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Could not resolve com.google.firebase:...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you're dealing with a dependency resolution problem.&lt;/p&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Missing environment variable: API_URL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you know exactly where to look.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The final error tells you that the build stopped. The underlying error tells you why.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-if-your-error-doesn-t-match-any-of-these-&quot;&gt;What If Your Error Doesn't Match Any Of These?&lt;/h2&gt;
&lt;p&gt;This is where things get much more frustrating.&lt;/p&gt;
&lt;p&gt;You can search the exact error message and find ten different Stack Overflow answers.&lt;/p&gt;
&lt;p&gt;One says to upgrade Gradle.&lt;/p&gt;
&lt;p&gt;Another says to delete &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another says to run &lt;code&gt;expo prebuild --clean&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Someone else says to downgrade React Native.&lt;/p&gt;
&lt;p&gt;And suddenly you're changing five different things without knowing which one actually matters.&lt;/p&gt;
&lt;p&gt;I've made that mistake myself.&lt;/p&gt;
&lt;p&gt;The problem with large React Native and Expo build logs is that they often contain &lt;strong&gt;a lot of information but very little prioritization&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You don't necessarily need another list of 15 possible fixes.&lt;/p&gt;
&lt;p&gt;You need to know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Which part of this particular log is actually causing the failure?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's also the problem I wanted to solve with &lt;strong&gt;FixMyError&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of manually digging through a huge React Native or Expo error log and trying to decide which line matters, you can paste the log into &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt; and get a focused diagnosis aimed at identifying the most likely cause and the first fix worth trying.&lt;/p&gt;
&lt;p&gt;It's particularly useful when your error doesn't fit neatly into one of the common categories above and you're staring at a wall of Gradle, Expo, or native build output wondering where to even start.&lt;/p&gt;
&lt;p&gt;You don't need to use it for every error. If the log clearly tells you what's wrong, fix it and move on.&lt;/p&gt;
&lt;p&gt;But when the useful information is buried somewhere inside a massive build log, that's exactly when a diagnostic tool becomes useful.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;a-simple-workflow-i-use-now&quot;&gt;A Simple Workflow I Use Now&lt;/h2&gt;
&lt;p&gt;When an Expo EAS build fails, I don't immediately start changing dependencies or upgrading packages.&lt;/p&gt;
&lt;p&gt;I go through this:&lt;/p&gt;
&lt;h4 id=&quot;1-find-the-actual-failure&quot;&gt;1. Find the actual failure&lt;/h4&gt;
&lt;p&gt;Don't stop at:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the underlying error.&lt;/p&gt;
&lt;h4 id=&quot;2-identify-the-category&quot;&gt;2. Identify the category&lt;/h4&gt;
&lt;p&gt;Is it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;configuration?&lt;/li&gt;
&lt;li&gt;dependency?&lt;/li&gt;
&lt;li&gt;Android/Gradle?&lt;/li&gt;
&lt;li&gt;iOS/Xcode?&lt;/li&gt;
&lt;li&gt;environment variables?&lt;/li&gt;
&lt;li&gt;credentials?&lt;/li&gt;
&lt;li&gt;native module compatibility?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-ask-what-changed&quot;&gt;3. Ask what changed&lt;/h4&gt;
&lt;p&gt;What did you install, upgrade, or modify immediately before the failure?&lt;/p&gt;
&lt;h4 id=&quot;4-check-the-full-log&quot;&gt;4. Check the full log&lt;/h4&gt;
&lt;p&gt;Don't assume the last error line is the root cause.&lt;/p&gt;
&lt;h4 id=&quot;5-run-the-relevant-diagnostic-tools&quot;&gt;5. Run the relevant diagnostic tools&lt;/h4&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and inspect your EAS configuration and dependencies.&lt;/p&gt;
&lt;h4 id=&quot;6-change-one-thing-at-a-time&quot;&gt;6. Change one thing at a time&lt;/h4&gt;
&lt;p&gt;Don't simultaneously upgrade Expo, downgrade Gradle, delete &lt;code&gt;node_modules&lt;/code&gt;, regenerate native projects, and change your dependencies.&lt;/p&gt;
&lt;p&gt;If the build starts working afterward, you won't know what actually fixed it.&lt;/p&gt;
&lt;h4 id=&quot;7-if-the-log-is-still-unclear-diagnose-the-log-itself&quot;&gt;7. If the log is still unclear, diagnose the log itself&lt;/h4&gt;
&lt;p&gt;That's where a tool like FixMyError can save time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-6&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;EAS Build failed&lt;/code&gt; message can look like a single problem, but it really isn't.&lt;/p&gt;
&lt;p&gt;EAS is just the environment where the failure becomes visible.&lt;/p&gt;
&lt;p&gt;The actual cause might be hiding in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expo configuration&lt;/li&gt;
&lt;li&gt;native dependencies&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;Xcode&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;credentials&lt;/li&gt;
&lt;li&gt;config plugins&lt;/li&gt;
&lt;li&gt;or a package that isn't compatible with your current SDK&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The biggest improvement you can make to your debugging process is to stop treating:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;as the error.&lt;/p&gt;
&lt;p&gt;Treat it as the &lt;strong&gt;symptom&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Then work backward until you find the first specific failure that actually explains what happened.&lt;/p&gt;
&lt;p&gt;And if you're staring at a huge build log and can't tell which of the hundreds of lines actually matters, that's precisely the kind of situation where having something analyze the error for you can be useful.&lt;/p&gt;
&lt;p&gt;Happy debugging.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/24440/how-to-fix-eas-build-failed-errors-in-expo</guid>
<pubDate>Tue, 11 Aug 2026 11:46:41 +0000</pubDate>
</item>
<item>
<title>The Bug Wasn't in the Code: Why Developers Debug the Wrong Layer</title>
<link>https://coderlegion.com/24428/the-bug-wasnt-in-the-code-why-developers-debug-the-wrong-layer</link>
<description>&lt;h2 id=&quot;the-bug-wasn-t-in-the-code-why-developers-debug-the-wrong-layer&quot;&gt;The Bug Wasn't in the Code: Why Developers Debug the Wrong Layer&lt;/h2&gt;
&lt;p&gt;One of the most frustrating debugging experiences is spending hours fixing code that was never actually broken.&lt;/p&gt;
&lt;p&gt;You inspect the function.&lt;/p&gt;
&lt;p&gt;You add logs.&lt;/p&gt;
&lt;p&gt;You rewrite the query.&lt;/p&gt;
&lt;p&gt;You restart the server.&lt;/p&gt;
&lt;p&gt;You blame the framework.&lt;/p&gt;
&lt;p&gt;And then you discover the real problem was a configuration variable, a stale cache, an expired token, or a request that never reached your application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sometimes the biggest debugging mistake is starting at the wrong layer.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-application-isn-t-the-whole-system&quot;&gt;The Application Isn't the Whole System&lt;/h3&gt;
&lt;p&gt;Modern applications rarely work in isolation.&lt;/p&gt;
&lt;p&gt;A simple button click might involve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browser&lt;/li&gt;
&lt;li&gt;Frontend application&lt;/li&gt;
&lt;li&gt;API gateway&lt;/li&gt;
&lt;li&gt;Backend service&lt;/li&gt;
&lt;li&gt;Authentication service&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Cache&lt;/li&gt;
&lt;li&gt;Message queue&lt;/li&gt;
&lt;li&gt;External API&lt;/li&gt;
&lt;li&gt;Cloud infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When something fails, developers often immediately open the backend code.&lt;/p&gt;
&lt;p&gt;But what if the request never reached the backend?&lt;/p&gt;
&lt;p&gt;Or what if the backend returned the correct response, but the frontend interpreted it incorrectly?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging should follow the path of the request, not your assumptions.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;start-with-the-first-known-failure&quot;&gt;Start With the First Known Failure&lt;/h3&gt;
&lt;p&gt;Instead of asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Where is the bug?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;Where does the system first behave differently from what I expected?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User clicks &quot;Save&quot;.&lt;/li&gt;
&lt;li&gt;Browser sends a request.&lt;/li&gt;
&lt;li&gt;API receives the request.&lt;/li&gt;
&lt;li&gt;Backend validates the data.&lt;/li&gt;
&lt;li&gt;Database stores the record.&lt;/li&gt;
&lt;li&gt;Backend returns success.&lt;/li&gt;
&lt;li&gt;Frontend updates the UI.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the UI doesn't show the saved record, don't immediately change the database query.&lt;/p&gt;
&lt;p&gt;First determine:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did the request leave the browser?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did the API receive it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did the database operation succeed?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did the response contain what the frontend expected?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This turns debugging from guessing into investigation.&lt;/p&gt;
&lt;h3 id=&quot;logs-are-evidence-not-decoration&quot;&gt;Logs Are Evidence, Not Decoration&lt;/h3&gt;
&lt;p&gt;A lot of applications have logs everywhere, but developers still struggle to debug them.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because the logs don't tell a story.&lt;/p&gt;
&lt;p&gt;A useful log should help answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What happened?&lt;/li&gt;
&lt;li&gt;When did it happen?&lt;/li&gt;
&lt;li&gt;Which request caused it?&lt;/li&gt;
&lt;li&gt;Which user/session/request ID was involved?&lt;/li&gt;
&lt;li&gt;Which service handled it?&lt;/li&gt;
&lt;li&gt;What was the result?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A request ID can be incredibly valuable.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Request ID: 8f21a

Frontend
→ POST /orders

API
→ Request received

Order Service
→ Payment validation started

Payment Service
→ Timeout

Order Service
→ Order creation failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you don't need to guess where the problem is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The system is showing you the path.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;don-t-change-five-things-at-once-1&quot;&gt;Don't Change Five Things at Once&lt;/h3&gt;
&lt;p&gt;This is one of the most common debugging mistakes.&lt;/p&gt;
&lt;p&gt;You see an error and change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The database query&lt;/li&gt;
&lt;li&gt;The API code&lt;/li&gt;
&lt;li&gt;The frontend state&lt;/li&gt;
&lt;li&gt;The configuration&lt;/li&gt;
&lt;li&gt;The dependency version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then the problem disappears.&lt;/p&gt;
&lt;p&gt;Great?&lt;/p&gt;
&lt;p&gt;Not really.&lt;/p&gt;
&lt;p&gt;You don't know which change fixed it.&lt;/p&gt;
&lt;p&gt;A better approach is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One hypothesis → one change → one observation.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This might feel slower, but it creates knowledge instead of luck.&lt;/p&gt;
&lt;h3 id=&quot;production-bugs-need-a-different-mindset&quot;&gt;Production Bugs Need a Different Mindset&lt;/h3&gt;
&lt;p&gt;Local development gives you a controlled environment.&lt;/p&gt;
&lt;p&gt;Production doesn't.&lt;/p&gt;
&lt;p&gt;Production may have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Different environment variables&lt;/li&gt;
&lt;li&gt;Different traffic&lt;/li&gt;
&lt;li&gt;Different database size&lt;/li&gt;
&lt;li&gt;Different permissions&lt;/li&gt;
&lt;li&gt;Different network rules&lt;/li&gt;
&lt;li&gt;Different caching behavior&lt;/li&gt;
&lt;li&gt;Different dependency versions&lt;/li&gt;
&lt;li&gt;Real concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's why:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;It works on my machine&quot; is not a debugging strategy.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You need to compare environments systematically.&lt;/p&gt;
&lt;h3 id=&quot;the-best-debuggers-think-in-layers&quot;&gt;The Best Debuggers Think in Layers&lt;/h3&gt;
&lt;p&gt;When something breaks, move through the system layer by layer:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Client&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Did the browser/mobile app send the expected request?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Network&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Did the request reach the correct destination?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Did the server receive and process it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Business Logic&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Did the application make the correct decision?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Database&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Was the data actually read or written?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Were containers, servers, queues, DNS, or cloud services healthy?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. External Dependencies&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Did a third-party service respond correctly?&lt;/p&gt;
&lt;p&gt;This simple mental model prevents you from spending two hours debugging a function when the actual problem is DNS.&lt;/p&gt;
&lt;h3 id=&quot;debugging-is-about-reducing-uncertainty&quot;&gt;Debugging Is About Reducing Uncertainty&lt;/h3&gt;
&lt;p&gt;Good debugging isn't about being the smartest person in the room.&lt;/p&gt;
&lt;p&gt;It's about systematically removing possibilities.&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Maybe the database is broken.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Can I prove the database received the query?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;The API must be returning bad data.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;What exact response did the API return?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;React isn't updating.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Did React receive the new state?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every question should reduce the number of possible explanations.&lt;/p&gt;
&lt;h3 id=&quot;final-thought-1&quot;&gt;Final Thought&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The fastest developers aren't necessarily the ones who type the fastest.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They're the ones who make fewer random changes.&lt;/p&gt;
&lt;p&gt;When a bug appears, don't immediately open the file where you think the problem lives.&lt;/p&gt;
&lt;p&gt;Follow the request.&lt;/p&gt;
&lt;p&gt;Collect evidence.&lt;/p&gt;
&lt;p&gt;Find the first unexpected behavior.&lt;/p&gt;
&lt;p&gt;Then fix the smallest thing that explains it.&lt;/p&gt;
&lt;p&gt;Because sometimes the code isn't broken.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your mental model of the system is.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sumita &lt;br&gt;
Web Developer&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24428/the-bug-wasnt-in-the-code-why-developers-debug-the-wrong-layer</guid>
<pubDate>Tue, 11 Aug 2026 07:06:55 +0000</pubDate>
</item>
<item>
<title>Has anyone actually used mini-swe-agent for real debugging or development?</title>
<link>https://coderlegion.com/24307/has-anyone-actually-used-mini-swe-agent-for-real-debugging-or-development</link>
<description>&lt;p&gt;DeepSWE's harness comparison made me curious, so I tried &lt;code&gt;mini-swe-agent&lt;/code&gt; myself on a matched set of debugging tasks with GPT-5.6 SOL at High reasoning.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/6b4b8a41-cca1-48c3-9d44-e8ae4740c6af&quot; alt=&quot;Live Debug / Tokens benchmark screenshot&quot;&gt;&lt;/p&gt;
&lt;p&gt;The current numbers surprised me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Codex CLI High: 151.91M tokens, 60% pass rate&lt;/li&gt;
&lt;li&gt;plain mini-swe-agent: 70.33M tokens, 67%&lt;/li&gt;
&lt;li&gt;the same mini harness plus macro execution, without changing the prompt: 60.59M tokens, 78%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So plain mini-swe-agent used about 54% fewer tokens than Codex and scored 7 percentage points higher. In the smaller ablation, changing only execution cut another ~14% of tokens and added 11 points.&lt;/p&gt;
&lt;p&gt;That does not prove it is universally better. It is one matched benchmark slice, and DeepSWE itself notes that prompt tuning may explain part of the harness gap. Still, for debugging, the very small “bash + linear history” setup looks unusually strong.&lt;/p&gt;
&lt;p&gt;Has anyone used it for day-to-day bug fixing or feature work? What breaks first outside a benchmark: repository setup, long-running tests, permissions, or patch quality?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://turaai.net/benchmark&quot;&gt;My runs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/swe-agent/mini-swe-agent&quot;&gt;mini-swe-agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://deepswe.datacurve.ai/blog/deepswe&quot;&gt;DeepSWE's explanation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Disclosure: I maintain Tura and ran the benchmark above.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24307/has-anyone-actually-used-mini-swe-agent-for-real-debugging-or-development</guid>
<pubDate>Sun, 09 Aug 2026 01:18:05 +0000</pubDate>
</item>
<item>
<title>Persister Cells: The Bug That Survives Every Retry Isn't Resistant, It's Dormant</title>
<link>https://coderlegion.com/24069/persister-cells-the-bug-that-survives-every-retry-isnt-resistant-its-dormant</link>
<description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/blog/persister-cells/&quot;&gt;Vibe Agent Making&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 1944, the physician Joseph Bigger hit a &lt;em&gt;Staphylococcus&lt;/em&gt; culture with penicillin and watched almost all of it die. A tiny remnant survived, which is unremarkable. The interesting part: he regrew the survivors and hit them again, and they died just as thoroughly as the first time. The survivors hadn't become tougher; their offspring were exactly as vulnerable. Whatever let that remnant live wasn't heritable and wasn't a matter of overpowering the drug. Bigger called them &quot;persisters,&quot; and the word is precise: they didn't &lt;em&gt;resist&lt;/em&gt; the penicillin, they &lt;em&gt;persisted&lt;/em&gt; through it. That distinction is the whole story of the most maddening class of bug you'll ever chase: the one that survives every restart, every retry, every careful remediation, and comes back identical, as if you'd done nothing.&lt;/p&gt;
&lt;h3 id=&quot;the-drug-needs-a-moving-target&quot;&gt;The drug needs a moving target&lt;/h3&gt;
&lt;p&gt;Most antibiotics don't work like poison on a fixed structure. They sabotage &lt;em&gt;growth&lt;/em&gt;: beta-lactams jam cell-wall construction (the cell tries to divide and bursts); quinolones corrupt DNA replication. The killing requires growth. A persister cell stops moving: it goes dormant, throttles its metabolism, does nothing, and a cell doing nothing presents nothing for a growth-targeting drug to attack. The 2019 &lt;em&gt;Nature Reviews Microbiology&lt;/em&gt; consensus paper calls the targets in non-growing cells &quot;recalcitrant&quot;: not defended, just absent from the fight. The drug washes harmlessly over the sleeping handful; treatment ends, they wake, and they rebuild the whole population. The infection relapses, genetically identical and fully susceptible. The survivors are not the strong ones. As Kim Lewis put it in 2007, the tolerance &quot;is not inherited and is reversible. Persisters are not mutants, but rather dormant cells.&quot; Strength was never the variable. Activity was.&lt;/p&gt;
&lt;h3 id=&quot;the-shape-of-the-failure-tells-you-what-it-is&quot;&gt;The shape of the failure tells you what it is&lt;/h3&gt;
&lt;p&gt;Persistence leaves a fingerprint. Plot survivors against time under treatment: a resistant strain gives a flat-ish line (it grows under the drug), but a persister population gives a two-phase curve, the bulk dying fast (a steep cliff) and then a long, stubborn tail the drug can't finish. That's the &lt;strong&gt;biphasic killing curve&lt;/strong&gt;, and it says: not resistance, dormancy.&lt;/p&gt;
&lt;p&gt;The vocabulary is load-bearing. &lt;strong&gt;Resistance&lt;/strong&gt;: a heritable change that lets a cell grow at drug concentrations that would kill its ancestors (it defeats the drug and passes it on). &lt;strong&gt;Tolerance&lt;/strong&gt;: a whole population dies more slowly without growing under the drug (buys time, not victory). &lt;strong&gt;Persistence&lt;/strong&gt;: a special case of tolerance, a &lt;em&gt;subpopulation&lt;/em&gt; that survives far better than the rest, the tail of the curve. The one-liner from the 2019 paper: resistant mutants can &lt;em&gt;divide&lt;/em&gt; during treatment; persisters can &lt;em&gt;only survive&lt;/em&gt;. One fights, the other waits.&lt;/p&gt;
&lt;h3 id=&quot;your-retry-is-the-antibiotic&quot;&gt;Your retry is the antibiotic&lt;/h3&gt;
&lt;p&gt;When a fault survives every restart and retry, the reflex is to call it &quot;hard&quot;, a deep, well-defended bug resisting your fix. Sometimes. Far more often it isn't resistant, it's dormant, and it survives for Bigger's reason: your remediation only hits the part of the system that's &lt;em&gt;active&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The cleanest documented case is anthropics/claude-code issue #23081: a connection drops, retry logic kicks in, and the retry keeps failing the same way because it reuses &lt;em&gt;stale connection state&lt;/em&gt;, the HTTP/2 pool, the cached TLS session, the DNS entry. The &quot;fresh&quot; attempt reaches back into the pool and re-serves the poisoned connection. The fix isn't to retry harder; it's to destroy and recreate the client (flush the pool and TLS cache) before retrying. Your retry was the antibiotic; it hit the active path with full force; the dormant connection survived precisely &lt;em&gt;because&lt;/em&gt; it was doing nothing when you struck.&lt;/p&gt;
&lt;p&gt;The shape is everywhere: the pool that hands back a dead connection after idle time (&quot;intermittent, only after a period of idleness when traffic picks back up&quot;); the lagging replica that's quiet during your fix on the primary and then reconnects and re-seeds stale state; the cached bad config, the queued-but-not-running job, the sleeping cron, the in-memory singleton holding a pre-fix value. Each is a place a fault can be dormant during your remediation. And the tell: the restart that &lt;em&gt;helps but doesn't last&lt;/em&gt;. Microsoft's docs on connection-pool exhaustion say restarting &quot;helps clear stale connections... typically a temporary solution rather than addressing root causes&quot;, which is the biphasic curve in plain English. A fix that works beautifully for ten minutes and relapses under load isn't flaky; it's a persister waking up.&lt;/p&gt;
&lt;h3 id=&quot;the-misdiagnosis-is-the-whole-trap&quot;&gt;The misdiagnosis is the whole trap&lt;/h3&gt;
&lt;p&gt;Resistance and persistence need &lt;em&gt;opposite&lt;/em&gt; responses, and mixing them up keeps you fighting forever. When a fix doesn't stick, the instinct is to escalate force: retry harder, restart more aggressively, add retries to the backoff. That's exactly right against a &lt;em&gt;resistant&lt;/em&gt; fault, one that actively defeats your fix and spreads under it. But against a persister, escalated force is worse than wasted, it's &lt;em&gt;invisible&lt;/em&gt;: force tuned to the active population can't touch what's asleep, by definition. Triple your retries against a stale pool and you re-serve the poisoned connection three times as fast. The persister isn't beating your fix; it's ignoring it. Hammering the active symptom while the dormant pool waits you out is the most common way good engineers lose days to a bug a five-minute cache flush would have ended.&lt;/p&gt;
&lt;p&gt;So the question is not &quot;how do I hit this harder?&quot; It's &quot;what part of this system was &lt;em&gt;asleep&lt;/em&gt; while I was fixing it?&quot; And there's a sting: the &lt;em&gt;Cell&lt;/em&gt; (2014) work links persistence to a higher risk of true resistance emerging during treatment, because the dormant survivors buy the time evolution needs. The software echo is faithful: the cached corruption that sits long enough to become the trusted value; the stale replica that, because it happened to be up, gets promoted to source of truth. Leave the reservoir unflushed and a recurring fault gets baked into structure. Clear it before it sets.&lt;/p&gt;
&lt;h3 id=&quot;the-clinician-s-discipline&quot;&gt;The clinician's discipline&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read the curve before you escalate.&lt;/strong&gt; Kills-most-then-a-stubborn-fraction-returns &lt;em&gt;is&lt;/em&gt; the diagnosis: a persister, not a resistant bug. More force is the wrong instrument.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enumerate the dormant reservoirs before you re-fix.&lt;/strong&gt; Explicitly, every time: caches, queues, idle pools, sleeping crons, offline/lagging replicas, persisted session state, in-memory singletons. Anywhere the fault could be asleep during remediation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flush, don't hammer.&lt;/strong&gt; Invalidate caches, drain queues, destroy and recreate the pool, quiesce and rebuild idle replicas. Eradicate the reservoir, don't escalate force on the visible symptom.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quiesce before you fix&lt;/strong&gt; (the most clinical move). Stop or drain the dormant population &lt;em&gt;first&lt;/em&gt; so it can't re-seed mid-repair: take the lagging replica out of rotation, drain the queue, empty the pool, fix the &lt;em&gt;quiet&lt;/em&gt; system, then bring the rest back. Otherwise you apply the same fix four times.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify across the relapse window.&lt;/strong&gt; Don't declare victory at the restart; confirm the fault doesn't return when load resumes and the idle parts wake, the moment the &quot;antibiotic concentration drops.&quot; That's finishing the full course instead of stopping when you feel better, which is exactly the behavior that breeds persisters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Survival isn't strength. The fault that laughs at every retry usually isn't tougher than your fix, it's asleep during it. Stop hitting the part that's awake. Find the part that's dormant, and flush the reservoir.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;An AI agent that keeps relapsing into the same failure is rarely fighting your fix; it's reusing dormant state the retry never touched, a cached belief, a stale piece of context, a poisoned memory carried across attempts. You can only find the reservoir if you can read what the agent actually did each time. &lt;a rel=&quot;nofollow&quot; href=&quot;https://vibeagentmaking.com/hosted-coc/&quot;&gt;Chain of Consciousness&lt;/a&gt; is that record: a tamper-evident chain of an agent's reasoning, tools, and actions across attempts, so you can see the biphasic curve in its behavior, spot the state that survived the restart, and flush it instead of hammering the part that was already awake. &lt;code&gt;pip install chain-of-consciousness&lt;/code&gt; / &lt;code&gt;npm install chain-of-consciousness&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24069/persister-cells-the-bug-that-survives-every-retry-isnt-resistant-its-dormant</guid>
<pubDate>Tue, 04 Aug 2026 03:36:09 +0000</pubDate>
</item>
<item>
<title>Why Great Developers Read Error Messages Before Writing Code</title>
<link>https://coderlegion.com/23898/why-great-developers-read-error-messages-before-writing-code</link>
<description>&lt;p&gt;Every developer has done it: you run your application, something breaks, and your first instinct is to start changing code. Five edits later, the original problem is still there—and now you've introduced two new bugs.&lt;/p&gt;
&lt;p&gt;The fastest developers aren't necessarily the best coders. They're often the best investigators.&lt;/p&gt;
&lt;p&gt;The Error Message Is Usually the First Clue&lt;/p&gt;
&lt;p&gt;Error messages aren't your enemy. They're your debugging assistant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Instead of ignoring them, ask these questions:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What file is mentioned?&lt;br&gt;
What line number is referenced?&lt;br&gt;
What exception occurred?&lt;br&gt;
What happened immediately before the error?&lt;/p&gt;
&lt;p&gt;A surprising number of issues can be solved just by carefully reading the entire stack trace.&lt;/p&gt;
&lt;p&gt;Stop Guessing, Start Testing&lt;/p&gt;
&lt;p&gt;Randomly changing code rarely solves complex problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A better workflow looks like this:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Reproduce the issue consistently.&lt;br&gt;
Read the complete error.&lt;br&gt;
Form one hypothesis.&lt;br&gt;
Test only that hypothesis.&lt;br&gt;
Repeat until the root cause is found.&lt;/p&gt;
&lt;p&gt;This approach is slower for the first five minutes—but much faster overall.&lt;/p&gt;
&lt;p&gt;Common Mistakes Developers Make&lt;br&gt;
Skipping the Stack Trace&lt;/p&gt;
&lt;p&gt;Many developers read only the first line of an error.&lt;/p&gt;
&lt;p&gt;The real answer is often buried several lines deeper.&lt;/p&gt;
&lt;p&gt;Copying the Error into AI Without Context&lt;/p&gt;
&lt;p&gt;AI can help, but if you only paste the error message, you'll often get generic advice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Include:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The relevant code&lt;br&gt;
Framework version&lt;br&gt;
Expected behavior&lt;br&gt;
Actual behavior&lt;br&gt;
Steps to reproduce&lt;/p&gt;
&lt;p&gt;Better context produces better answers.&lt;/p&gt;
&lt;p&gt;Fixing Symptoms Instead of Causes&lt;/p&gt;
&lt;p&gt;Imagine your application crashes because a value is null.&lt;/p&gt;
&lt;p&gt;Changing:&lt;/p&gt;
&lt;p&gt;if (user) {&lt;br&gt;
   ...&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;might stop the crash.&lt;/p&gt;
&lt;p&gt;But why was user null in the first place?&lt;/p&gt;
&lt;p&gt;That's the real bug.&lt;/p&gt;
&lt;p&gt;Build a Debugging Mindset&lt;/p&gt;
&lt;p&gt;Professional developers don't panic when something breaks.&lt;/p&gt;
&lt;p&gt;They collect evidence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Think like a detective:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What changed recently?&lt;br&gt;
Can I reproduce it?&lt;br&gt;
Does it happen everywhere or only sometimes?&lt;br&gt;
What assumptions am I making?&lt;/p&gt;
&lt;p&gt;These questions often reveal the answer before you even touch the code.&lt;/p&gt;
&lt;p&gt;Use Your Tools&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modern IDEs and browsers provide incredible debugging tools:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Breakpoints&lt;br&gt;
Variable inspection&lt;br&gt;
Network monitoring&lt;br&gt;
Performance profiling&lt;br&gt;
Log filtering&lt;/p&gt;
&lt;p&gt;Learning these tools will save far more time than memorizing another framework.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Writing code is only half of software development.&lt;/p&gt;
&lt;p&gt;The other half is understanding why code behaves the way it does.&lt;/p&gt;
&lt;p&gt;The next time an error appears, resist the urge to immediately start editing files.&lt;/p&gt;
&lt;p&gt;Read first. Think second. Change code last.&lt;/p&gt;
&lt;p&gt;You'll solve bugs faster—and become a much stronger developer in the process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sumita &lt;br&gt;
Web Developer&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23898/why-great-developers-read-error-messages-before-writing-code</guid>
<pubDate>Fri, 31 Jul 2026 17:13:54 +0000</pubDate>
</item>
<item>
<title>Why Most Developers Never Read Their Own Logs (And It Costs Them Hours)</title>
<link>https://coderlegion.com/23844/why-most-developers-never-read-their-own-logs-and-it-costs-them-hours</link>
<description>&lt;p&gt;When something breaks, many developers immediately start changing code, adding console.log() statements, or searching Stack Overflow. Ironically, the answer is often already sitting in the application's logs—they just never read them carefully.&lt;/p&gt;
&lt;p&gt;Logs aren't just for production monitoring. They're one of the fastest ways to understand what your application is actually doing.&lt;/p&gt;
&lt;p&gt;The Hidden Cost of Ignoring Logs&lt;/p&gt;
&lt;p&gt;Imagine a user reports that checkout fails randomly.&lt;/p&gt;
&lt;p&gt;Many developers will:&lt;/p&gt;
&lt;p&gt;Add more debug statements.&lt;br&gt;
Restart the application.&lt;br&gt;
Try to reproduce the issue dozens of times.&lt;br&gt;
Assume it's a frontend bug.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Meanwhile, the existing logs already show:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Database connection timeout&lt;br&gt;
Third-party API returning 429 (Rate Limit)&lt;br&gt;
Authentication token expired&lt;br&gt;
Missing environment variable&lt;/p&gt;
&lt;p&gt;The information was there all along.&lt;/p&gt;
&lt;p&gt;Good Logs Tell a Story&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A useful log answers questions like:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What happened?&lt;br&gt;
When did it happen?&lt;br&gt;
Which user or request was affected?&lt;br&gt;
Which service generated the error?&lt;br&gt;
What happened immediately before the failure?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Instead of logging:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Error occurred&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Log something meaningful:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Order creation failed&lt;br&gt;
Order ID: 48291&lt;br&gt;
User: 1542&lt;br&gt;
Payment Gateway: Stripe&lt;br&gt;
Status: Timeout&lt;br&gt;
Retry Count: 2&lt;/p&gt;
&lt;p&gt;Now debugging takes minutes instead of hours.&lt;/p&gt;
&lt;p&gt;Logging Is Not Just About Errors&lt;/p&gt;
&lt;p&gt;Many developers only log exceptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The best systems also log:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;User authentication&lt;br&gt;
API requests&lt;br&gt;
Database execution time&lt;br&gt;
Cache hits and misses&lt;br&gt;
Background jobs&lt;br&gt;
Queue processing&lt;br&gt;
External API calls&lt;/p&gt;
&lt;p&gt;These events help explain why an issue happened—not just that it happened.&lt;/p&gt;
&lt;p&gt;Avoid These Common Mistakes&lt;/p&gt;
&lt;p&gt;❌ Logging passwords or sensitive data&lt;/p&gt;
&lt;p&gt;❌ Writing huge log files with unnecessary information&lt;/p&gt;
&lt;p&gt;❌ Using different log formats across services&lt;/p&gt;
&lt;p&gt;❌ Ignoring warning messages&lt;/p&gt;
&lt;p&gt;❌ Never rotating or cleaning log files&lt;/p&gt;
&lt;p&gt;Structured Logs Make Life Easier&lt;/p&gt;
&lt;p&gt;Instead of plain text, many teams use structured JSON logs.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;{&lt;br&gt;
  &quot;level&quot;:&quot;ERROR&quot;,&lt;br&gt;
  &quot;service&quot;:&quot;payment-api&quot;,&lt;br&gt;
  &quot;userId&quot;:1542,&lt;br&gt;
  &quot;orderId&quot;:48291,&lt;br&gt;
  &quot;message&quot;:&quot;Gateway timeout&quot;&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;These logs are much easier to search and analyze using tools like Elasticsearch, Grafana, or cloud logging platforms.&lt;/p&gt;
&lt;p&gt;Build the Habit&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next time you face a bug:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Read the logs first.&lt;br&gt;
Follow the request from start to finish.&lt;br&gt;
Look for warnings before the error.&lt;br&gt;
Check timestamps.&lt;br&gt;
Verify external service responses.&lt;/p&gt;
&lt;p&gt;You'll often solve the issue before writing a single new line of code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Great debugging isn't about guessing—it's about gathering evidence. Your logs are one of the richest sources of that evidence. The more intentionally you log today, the less time you'll spend chasing mysterious bugs tomorrow.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23844/why-most-developers-never-read-their-own-logs-and-it-costs-them-hours</guid>
<pubDate>Fri, 31 Jul 2026 08:09:41 +0000</pubDate>
</item>
<item>
<title>How to Fix &quot;Duplicate class ... found in modules&quot; in React Native &amp; Expo</title>
<link>https://coderlegion.com/23340/how-to-fix-duplicate-class-found-in-modules-in-react-native-expo</link>
<description>&lt;p&gt;If you've been developing with React Native or Expo for a while, chances are you've had a project that suddenly refuses to build for what seems like no reason.&lt;/p&gt;
&lt;p&gt;Maybe you installed a new package. Maybe you upgraded Expo. Maybe you only changed a single dependency. You confidently run your Android build, expecting it to compile like always...&lt;/p&gt;
&lt;p&gt;Instead, Gradle greets you with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:checkDebugDuplicateClasses'.

Duplicate class androidx.lifecycle.ViewModelLazy found in modules lifecycle-viewmodel-2.8.2.aar and lifecycle-viewmodel-ktx-2.6.1.aar

Duplicate class com.google.android.gms.internal.measurement.zzab found in modules play-services-measurement-base-22.0.0.aar and play-services-measurement-impl-21.6.2.aar

Duplicate class ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, the error looks straightforward: &lt;em&gt;there are duplicate classes&lt;/em&gt;. Easy enough, right?&lt;/p&gt;
&lt;p&gt;Not exactly.&lt;/p&gt;
&lt;p&gt;In reality, this is one of those Android build errors that can send you down a rabbit hole of Gradle files, dependency trees and Stack Overflow threads, only to realize the real cause was something completely different.&lt;/p&gt;
&lt;p&gt;Let's break down what's actually happening and, more importantly, how to fix it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-this-error-happens&quot;&gt;Why this error happens&lt;/h2&gt;
&lt;p&gt;Every Android library included in your project contains compiled Java or Kotlin classes.&lt;/p&gt;
&lt;p&gt;When Gradle builds your application, it combines all of those libraries into a single APK or AAB.&lt;/p&gt;
&lt;p&gt;If two different dependencies contain the exact same class, Gradle doesn't know which version should be packaged.&lt;/p&gt;
&lt;p&gt;Instead of guessing, it stops the build with a &lt;strong&gt;Duplicate class&lt;/strong&gt; error.&lt;/p&gt;
&lt;p&gt;The difficult part is that the dependency causing the conflict usually isn't the one shown in the error.&lt;/p&gt;
&lt;p&gt;Very often, it's another package pulling in an older or incompatible version behind the scenes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-most-common-causes&quot;&gt;The most common causes&lt;/h2&gt;
&lt;p&gt;After seeing this error many times, these are usually the culprits.&lt;/p&gt;
&lt;h3 id=&quot;1-two-libraries-depend-on-different-versions-of-the-same-package&quot;&gt;1. Two libraries depend on different versions of the same package&lt;/h3&gt;
&lt;p&gt;This is by far the most common scenario.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Library A requires:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;androidx.lifecycle:lifecycle-viewmodel:2.8.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while Library B depends on:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;androidx.lifecycle:lifecycle-viewmodel:2.6.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both versions get added to your dependency graph, eventually leading to duplicate classes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-an-outdated-react-native-package&quot;&gt;2. An outdated React Native package&lt;/h3&gt;
&lt;p&gt;Older React Native libraries often haven't been updated for newer AndroidX or Google Play Services versions.&lt;/p&gt;
&lt;p&gt;Even though your project is current, that one dependency may still reference libraries released years ago.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-expo-sdk-upgrades&quot;&gt;3. Expo SDK upgrades&lt;/h3&gt;
&lt;p&gt;After upgrading Expo, many native dependency versions change.&lt;/p&gt;
&lt;p&gt;If one third-party package hasn't caught up yet, Gradle may end up resolving incompatible versions.&lt;/p&gt;
&lt;p&gt;This is especially common immediately after major SDK releases.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-mixing-manual-native-configuration-with-expo-managed-dependencies&quot;&gt;4. Mixing manual native configuration with Expo-managed dependencies&lt;/h3&gt;
&lt;p&gt;Sometimes developers manually add Android dependencies that Expo already manages internally.&lt;/p&gt;
&lt;p&gt;The result is two copies of essentially the same library.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-to-fix-it&quot;&gt;How to fix it&lt;/h2&gt;
&lt;p&gt;There isn't one universal solution, but these steps solve the majority of cases.&lt;/p&gt;
&lt;h3 id=&quot;step-1-read-the-duplicated-class-carefully&quot;&gt;Step 1 — Read the duplicated class carefully&lt;/h3&gt;
&lt;p&gt;Don't focus on the entire error.&lt;/p&gt;
&lt;p&gt;Look specifically for the two modules involved.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;lifecycle-viewmodel-2.8.2

lifecycle-viewmodel-ktx-2.6.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those names usually point you toward the conflicting dependency.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-find-who-is-importing-it&quot;&gt;Step 2 — Find who is importing it&lt;/h3&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./gradlew app:dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./gradlew app:dependencyInsight --dependency lifecycle-viewmodel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows which package is pulling each version into your project.&lt;/p&gt;
&lt;p&gt;It's much faster than guessing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-update-outdated-packages&quot;&gt;Step 3 — Update outdated packages&lt;/h3&gt;
&lt;p&gt;If one library is significantly behind, updating it often resolves the conflict immediately.&lt;/p&gt;
&lt;p&gt;Always check whether a newer version exists before trying complicated Gradle workarounds.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-clean-the-project&quot;&gt;Step 4 — Clean the project&lt;/h3&gt;
&lt;p&gt;Sometimes Gradle continues using cached artifacts.&lt;/p&gt;
&lt;p&gt;Try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android

./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then rebuild.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-5-regenerate-native-files-expo-&quot;&gt;Step 5 — Regenerate native files (Expo)&lt;/h3&gt;
&lt;p&gt;If you're using Expo Prebuild:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo prebuild --clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This recreates the native Android project using your current configuration.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-6-force-dependency-versions-only-if-necessary-&quot;&gt;Step 6 — Force dependency versions (only if necessary)&lt;/h3&gt;
&lt;p&gt;Sometimes you need to force Gradle to use one specific version.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;configurations.all {
    resolutionStrategy {
        force &quot;androidx.lifecycle:lifecycle-viewmodel:2.8.2&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this carefully.&lt;/p&gt;
&lt;p&gt;If two libraries genuinely require incompatible versions, forcing one version may create runtime problems later.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-to-avoid-this-error-in-the-future&quot;&gt;How to avoid this error in the future&lt;/h2&gt;
&lt;p&gt;While duplicate class errors can't always be prevented, these habits reduce the chances considerably.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep Expo SDKs reasonably up to date.&lt;/li&gt;
&lt;li&gt;Avoid abandoned React Native libraries.&lt;/li&gt;
&lt;li&gt;Update dependencies together instead of one at a time.&lt;/li&gt;
&lt;li&gt;Review package compatibility before major upgrades.&lt;/li&gt;
&lt;li&gt;Avoid manually adding Android dependencies unless absolutely necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-the-error-isn-t-actually-the-problem&quot;&gt;When the error isn't actually the problem&lt;/h2&gt;
&lt;p&gt;One thing that makes this error frustrating is that the duplicate classes are often just a symptom.&lt;/p&gt;
&lt;p&gt;The real issue might be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an outdated library,&lt;/li&gt;
&lt;li&gt;conflicting Firebase dependencies,&lt;/li&gt;
&lt;li&gt;mixed AndroidX versions,&lt;/li&gt;
&lt;li&gt;incompatible Google Play Services packages,&lt;/li&gt;
&lt;li&gt;or an incomplete Expo migration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's why simply searching the duplicated class name doesn't always lead to the correct fix.&lt;/p&gt;
&lt;p&gt;Understanding &lt;strong&gt;why&lt;/strong&gt; those dependencies ended up together is usually more important than the class itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-fixmyerror-can-help&quot;&gt;How FixMyError can help&lt;/h2&gt;
&lt;p&gt;Instead of manually digging through Gradle output, dependency trees and dozens of forum posts, you can paste the full error into &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The tool analyzes the build log, identifies the likely source of the conflict, explains what's happening in plain English, and suggests fixes based on the specific dependencies involved.&lt;/p&gt;
&lt;p&gt;Rather than treating every duplicate class error as identical, it tries to narrow the problem down to the actual cause behind your particular build failure.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-7&quot;&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;Duplicate class errors are among the most frustrating Android build issues because the message rarely points directly to the package that's responsible.&lt;/p&gt;
&lt;p&gt;The good news is that, in most cases, the problem comes down to dependency resolution rather than something fundamentally broken in your project.&lt;/p&gt;
&lt;p&gt;Once you identify which libraries are introducing conflicting versions, the fix usually becomes much more straightforward.&lt;/p&gt;
&lt;p&gt;If you're currently staring at a wall of Gradle output wondering where to begin, start by identifying the conflicting modules, trace where they're coming from, and work outward from there.&lt;/p&gt;
&lt;p&gt;It takes a bit of patience, but it's almost always easier than blindly trying random fixes from different Stack Overflow threads.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/23340/how-to-fix-duplicate-class-found-in-modules-in-react-native-expo</guid>
<pubDate>Thu, 23 Jul 2026 12:36:11 +0000</pubDate>
</item>
<item>
<title>The Bug That Wasn't a Bug: Why Your First Instinct Is Almost Always Wrong</title>
<link>https://coderlegion.com/23131/the-bug-that-wasnt-a-bug-why-your-first-instinct-is-almost-always-wrong</link>
<description>&lt;p&gt;You've been staring at the same 40 lines of code for two hours. You're convinced the problem is in the database query. You've rewritten it three times. You've Googled the same error message so many times that it's now in your browser autocomplete. You've blamed the ORM, the driver, the network, the moon.&lt;/p&gt;
&lt;p&gt;The bug is in line 7. It's a missing &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sound familiar? Welcome to debugging — where your first instinct is almost always wrong, and the sooner you accept that, the faster you actually fix things.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-curse-of-the-expert-guesser&quot;&gt;The Curse of the Expert Guesser&lt;/h3&gt;
&lt;p&gt;Here's what happens when most developers hit a bug:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They read the error message (briefly)&lt;/li&gt;
&lt;li&gt;They form a theory about the cause&lt;/li&gt;
&lt;li&gt;They go looking for &lt;em&gt;evidence that confirms the theory&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;They find &lt;em&gt;something&lt;/em&gt; that looks like evidence&lt;/li&gt;
&lt;li&gt;They fix the wrong thing&lt;/li&gt;
&lt;li&gt;They are confused when it doesn't work&lt;/li&gt;
&lt;li&gt;They repeat steps 1–6 three more times&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is called &lt;strong&gt;confirmation bias&lt;/strong&gt;, and it is absolutely devastating in debugging. Your brain is not trying to find the truth — it's trying to prove itself right. And it's annoyingly good at that.&lt;/p&gt;
&lt;p&gt;The moment you think &quot;I bet it's the cache,&quot; you will find seventeen things that look like cache problems. The cache is probably fine. The cache is almost always fine.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix-treat-debugging-like-a-science-experiment&quot;&gt;The Fix: Treat Debugging Like a Science Experiment&lt;/h3&gt;
&lt;p&gt;Good debugging is not guessing with confidence. It's forming a &lt;strong&gt;hypothesis&lt;/strong&gt; and then actively trying to &lt;em&gt;disprove&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;Ask yourself: &quot;If I am wrong about this, what would that look like?&quot;&lt;/p&gt;
&lt;p&gt;If your theory is that the database query is slow, what does it look like if the query is actually fast? Check. Actually measure. Don't assume. The fastest way to find a bug is to ruthlessly eliminate what the bug is &lt;em&gt;not&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This sounds obvious. Nobody does it.&lt;/p&gt;
&lt;p&gt;Instead, most of us do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Something is wrong. Let's add print statements everywhere.
print(&quot;here 1&quot;)
print(&quot;here 2&quot;)
print(data)
print(&quot;here 3 - still broken&quot;)
print(&quot;WHY&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is fine — but only if you're actually reading the output and updating your theory based on what you see, not just hunting for confirmation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-reason-debugging-is-hard&quot;&gt;The Real Reason Debugging is Hard&lt;/h3&gt;
&lt;p&gt;It's not the complexity of the code. It's the &lt;strong&gt;distance between the symptom and the cause&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The error appears in the frontend. The bug is in the backend validation logic. The test fails in CI. The bug only exists when environment variables are loaded in a certain order. The app crashes in production. The bug is a timezone issue that only surfaces in UTC+2 on Tuesdays.&lt;/p&gt;
&lt;p&gt;Most bugs are not where they appear. They are &lt;em&gt;upstream&lt;/em&gt;. Something failed, something else caught it silently, something else passed bad data forward, and eventually something &lt;em&gt;blew up&lt;/em&gt; three layers away from where the original sin happened.&lt;/p&gt;
&lt;p&gt;So when you see an error, your first question shouldn't be &quot;what's broken here?&quot; It should be &quot;what gave this code bad input?&quot;&lt;/p&gt;
&lt;p&gt;Trace backwards. Always trace backwards.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;things-i-ve-learned-the-embarrassing-way&quot;&gt;Things I've Learned the Embarrassing Way&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Read the full error message.&lt;/strong&gt; Not the first line. The whole thing. The part you need is usually at the bottom, and you've been ignoring it because you formed a theory at line one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rubber duck it early.&lt;/strong&gt; The moment you start writing a Stack Overflow question and you have to actually &lt;em&gt;describe what you tried&lt;/em&gt;, you will frequently solve your own problem before you hit Post. The rubber duck does not care how long you've been coding. It just wants you to explain it clearly. Explaining it clearly forces you to think clearly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The last thing you changed broke it.&lt;/strong&gt; I know you don't want to hear this. I know you were just &quot;cleaning up.&quot; You broke it. Check git diff.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If it works on your machine and not in production, the environments are different.&lt;/strong&gt; Not slightly. Fundamentally. Find the difference before you do anything else. Environment variables, versions, file paths, memory limits — something is different. Find it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sleep is a debugging tool.&lt;/strong&gt; I don't know why this works. Your brain does something when you stop looking at it. You will wake up at 3 am and know exactly what's wrong. Happens to everyone.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-bug-that-wasn-t-a-bug-for-real-this-time-&quot;&gt;The Bug That Wasn't a Bug (For Real This Time)&lt;/h3&gt;
&lt;p&gt;Once, I spent an entire afternoon convinced a third-party API was returning inconsistent data. I logged everything. I added retries. I considered switching providers. I drafted an angry support ticket.&lt;/p&gt;
&lt;p&gt;The API was fine.&lt;/p&gt;
&lt;p&gt;I was passing a parameter name with a capital letter instead of lowercase. The API silently ignored it and returned defaults every single time. Consistent. Predictable. Not a bug at all. Completely my fault.&lt;/p&gt;
&lt;p&gt;The real lesson isn't &quot;check your parameter names&quot; (although yes, check your parameter names). The lesson is that the system was behaving &lt;em&gt;exactly as designed&lt;/em&gt;. I was wrong about the contract. My understanding was the bug, not the code.&lt;/p&gt;
&lt;p&gt;A lot of bugs are like this. The code is doing exactly what you told it to do. You told it to do the wrong thing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;one-practical-habit-that-will-actually-help&quot;&gt;One Practical Habit That Will Actually Help&lt;/h3&gt;
&lt;p&gt;When you've been stuck for more than 30 minutes, write down — physically or in a comment — exactly what you believe is happening and why. Not what you want to be happening. What you believe is &lt;em&gt;actually&lt;/em&gt; happening right now.&lt;/p&gt;
&lt;p&gt;Then look at what you wrote. Is each sentence something you &lt;em&gt;measured&lt;/em&gt;, or something you &lt;em&gt;assumed&lt;/em&gt;? The assumptions are where the bug lives.&lt;/p&gt;
&lt;p&gt;Most bugs are not mysterious. They're just hiding inside something you were too confident about to check.&lt;/p&gt;
&lt;p&gt;Go check it.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23131/the-bug-that-wasnt-a-bug-why-your-first-instinct-is-almost-always-wrong</guid>
<pubDate>Tue, 21 Jul 2026 09:59:28 +0000</pubDate>
</item>
<item>
<title>How to fix `global._getAnimationTimestamp is not a function` After Upgrading to Expo SDK 55</title>
<link>https://coderlegion.com/22798/how-to-fix-global-getanimationtimestamp-is-not-a-function-after-upgrading-to-expo-sdk-55</link>
<description>&lt;p&gt;I recently spent far more time than I'd like to admit chasing down a hard crash after upgrading a project from Expo SDK 54 to 55.&lt;/p&gt;
&lt;p&gt;The app launched fine. Navigation worked. Everything looked normal.&lt;/p&gt;
&lt;p&gt;Then the moment a Reanimated animation fired, the app exploded with a red screen:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Render Error

global._getAnimationTimestamp is not a function (it is undefined)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, the crash happened inside a &lt;code&gt;useEffect&lt;/code&gt; that called &lt;code&gt;withTiming()&lt;/code&gt; on a shared value when a tab gained focus.&lt;/p&gt;
&lt;p&gt;My first assumption was a dependency mismatch between &lt;code&gt;react-native-reanimated&lt;/code&gt; and &lt;code&gt;react-native-worklets&lt;/code&gt;. I checked versions, verified the dependency tree, and even started looking for Babel configuration issues.&lt;/p&gt;
&lt;p&gt;Everything looked correct.&lt;/p&gt;
&lt;p&gt;As it turns out, the problem wasn't in the JavaScript code at all.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-actually-happening-&quot;&gt;What Is Actually Happening?&lt;/h3&gt;
&lt;p&gt;At first glance, this error looks like a breaking change inside Reanimated or a bad project configuration.&lt;/p&gt;
&lt;p&gt;In reality, it's usually a sync problem between your JavaScript bundle and your native application binary.&lt;/p&gt;
&lt;p&gt;The important clue is the function name itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;_getAnimationTimestamp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't a function your application calls directly. It's one of the internal functions Reanimated expects to find in the native runtime.&lt;/p&gt;
&lt;p&gt;Reanimated relies heavily on JSI (JavaScript Interface) to bridge JavaScript directly to native C++ code for performance-critical animation work.&lt;/p&gt;
&lt;p&gt;When you upgrade to Expo SDK 55, Reanimated gets upgraded as well (typically from 4.1.x to 4.2.x, depending on your project).&lt;/p&gt;
&lt;p&gt;Your Metro server immediately starts serving the updated JavaScript bundle.&lt;/p&gt;
&lt;p&gt;The problem is that your custom development client is still running the native code that was compiled before the upgrade.&lt;/p&gt;
&lt;p&gt;Now you have two different versions talking to each other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The JavaScript side expects the newer Reanimated runtime.&lt;/li&gt;
&lt;li&gt;The native side is still exposing the older implementation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The updated JavaScript bundle expects &lt;code&gt;_getAnimationTimestamp&lt;/code&gt; to exist.&lt;/p&gt;
&lt;p&gt;Your older native client doesn't provide that function yet.&lt;/p&gt;
&lt;p&gt;The moment Reanimated tries to use it, the app crashes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-reinstalling-dependencies-doesn-t-help&quot;&gt;Why Reinstalling Dependencies Doesn't Help&lt;/h3&gt;
&lt;p&gt;I went through the usual checklist first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reinstall dependencies&lt;/li&gt;
&lt;li&gt;Verify package versions&lt;/li&gt;
&lt;li&gt;Check Babel configuration&lt;/li&gt;
&lt;li&gt;Clear &lt;code&gt;node_modules&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm install&lt;/code&gt; again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of it fixed the issue.&lt;/p&gt;
&lt;p&gt;That's because the problem isn't living in your JavaScript dependencies.&lt;/p&gt;
&lt;p&gt;Your packages can be perfectly correct while your device or simulator is still running an outdated native binary.&lt;/p&gt;
&lt;p&gt;As long as the native client wasn't rebuilt after the upgrade, the crash will continue to happen.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-you-ll-usually-see-this-error&quot;&gt;When You'll Usually See This Error&lt;/h3&gt;
&lt;p&gt;One thing worth mentioning is that this mostly shows up when you're using a custom development client.&lt;/p&gt;
&lt;p&gt;If you're using Expo Go, Expo manages the native runtime for you, so JavaScript and native code generally stay in sync.&lt;/p&gt;
&lt;p&gt;This issue is much more common when you're working with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom dev-clients&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expo run:android&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expo run:ios&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Development builds generated through EAS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those environments, it's entirely possible for Metro to be serving new JavaScript while your device is still running an older native build.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix&quot;&gt;The Fix&lt;/h3&gt;
&lt;p&gt;A normal JavaScript reload won't solve this.&lt;/p&gt;
&lt;p&gt;Pressing &lt;code&gt;r&lt;/code&gt; in the Metro terminal only refreshes the JavaScript bundle. It does not rebuild the native application.&lt;/p&gt;
&lt;p&gt;To fix the issue, you need both sides of the application running the same version.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-1-clear-metro-s-cache&quot;&gt;Step 1: Clear Metro's Cache&lt;/h4&gt;
&lt;p&gt;Stop Metro completely and restart it with a clean cache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This removes the possibility of Metro serving stale transformed files.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-rebuild-the-native-client&quot;&gt;Step 2: Rebuild the Native Client&lt;/h4&gt;
&lt;p&gt;If you're building locally, generate a fresh native application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo run:ios

# or

npx expo run:android
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This recompiles the native code using the versions currently installed in your project.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-rebuild-your-development-build-eas-&quot;&gt;Step 3: Rebuild Your Development Build (EAS)&lt;/h4&gt;
&lt;p&gt;If you're using EAS development builds, you'll need to generate a completely new build and install it on your device.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build --profile development
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the build finishes, install the new IPA or APK and launch the updated client.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-fix-works&quot;&gt;Why This Fix Works&lt;/h3&gt;
&lt;p&gt;After rebuilding, both sides of the application are finally using the same version of Reanimated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Metro serves the updated JavaScript bundle.&lt;/li&gt;
&lt;li&gt;The native binary contains the matching JSI bindings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once those versions line up again, Reanimated can find the functions it expects and the crash disappears.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-quick-note-on-debugging&quot;&gt;A Quick Note on Debugging&lt;/h3&gt;
&lt;p&gt;This was one of those bugs that initially looked like a dependency issue, then a Babel issue, then a Reanimated issue.&lt;/p&gt;
&lt;p&gt;In reality, it was just an out-of-date native build.&lt;/p&gt;
&lt;p&gt;Problems like this are one of the more frustrating parts of React Native because the error message rarely points to the actual cause. It's easy to spend hours looking at package versions when the real fix is simply rebuilding the native client.&lt;/p&gt;
&lt;p&gt;After spending enough evenings digging through stack traces, GitHub issues, and old forum threads, I ended up building a small tool called &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;Fix My Error&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You paste in the error output, and it helps narrow down likely causes and fixes without having to bounce between ten different tabs looking for someone who hit the same problem three years ago.&lt;/p&gt;
&lt;p&gt;For this particular error, though, the fix turned out to be much simpler than I expected: clear Metro, rebuild the native client, and make sure your JavaScript bundle and native runtime are actually running the same version.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/22798/how-to-fix-global-getanimationtimestamp-is-not-a-function-after-upgrading-to-expo-sdk-55</guid>
<pubDate>Thu, 16 Jul 2026 09:53:20 +0000</pubDate>
</item>
<item>
<title>How to Fix: &quot;Namespace not specified&quot; Gradle 8 Error in React Native &amp; Expo</title>
<link>https://coderlegion.com/22158/how-to-fix-namespace-not-specified-gradle-8-error-in-react-native-expo</link>
<description>&lt;h2 id=&quot;fixing-the-namespace-not-specified-error-in-react-native-and-expo-android-builds&quot;&gt;Fixing the &quot;Namespace Not Specified&quot; Error in React Native and Expo Android Builds&lt;/h2&gt;
&lt;p&gt;If you recently upgraded a project to React Native 0.73+ or bumped your Expo SDK to version 50+, you may have run into an Android build error that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring project ':react-native-some-old-library'.

&amp;gt; Namespace not specified for group 'react-native-some-old-library'
  and version '1.0.0' as per the Android Gradle Plugin 8.0+ guidelines.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, it looks like something is wrong with your app. In most cases, it isn't.&lt;/p&gt;
&lt;p&gt;The error is usually coming from an older dependency inside &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here's what's actually causing it and how to fix it properly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-root-cause&quot;&gt;The Root Cause&lt;/h3&gt;
&lt;p&gt;Before Android Gradle Plugin (AGP) 8.0, Android libraries could define their package name inside &lt;code&gt;AndroidManifest.xml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;com.someoldlibrary&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Starting with AGP 8.0, that is no longer enough.&lt;/p&gt;
&lt;p&gt;Every Android module is now expected to define a &lt;code&gt;namespace&lt;/code&gt; inside its &lt;code&gt;build.gradle&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;When you upgrade React Native or Expo, your Android tooling gets upgraded as well. The problem is that many older React Native libraries haven't been updated to follow the new requirement.&lt;/p&gt;
&lt;p&gt;As soon as Gradle encounters one of those libraries, it stops during configuration and the build fails.&lt;/p&gt;
&lt;p&gt;This started showing up much more frequently after Expo SDK 50 and newer React Native releases adopted newer Android tooling.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;before-you-change-anything&quot;&gt;Before You Change Anything&lt;/h3&gt;
&lt;p&gt;Look closely at the error message.&lt;/p&gt;
&lt;p&gt;Most of the time you'll see something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A problem occurred configuring project ':react-native-some-old-library'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That project name is usually the library causing the issue.&lt;/p&gt;
&lt;p&gt;A common mistake is to start editing your app's own Gradle files. In most cases, the problem is actually inside the dependency listed in the error.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix-use-patch-package&quot;&gt;The Fix: Use patch-package&lt;/h3&gt;
&lt;p&gt;Since the problem comes from a third-party library, editing files directly inside &lt;code&gt;node_modules&lt;/code&gt; isn't a real fix.&lt;/p&gt;
&lt;p&gt;The next time you run &lt;code&gt;npm install&lt;/code&gt;, your changes will be overwritten.&lt;/p&gt;
&lt;p&gt;Instead, make the change once and save it using &lt;code&gt;patch-package&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;step-1-find-the-package-name&quot;&gt;Step 1: Find the Package Name&lt;/h4&gt;
&lt;p&gt;Navigate to the library's manifest file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules/react-native-some-old-library/android/src/main/AndroidManifest.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for the package attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;manifest
    package=&quot;com.someoldlibrary&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy that value.&lt;/p&gt;
&lt;p&gt;You'll use it as the namespace in the next step.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-add-the-missing-namespace&quot;&gt;Step 2: Add the Missing Namespace&lt;/h4&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules/react-native-some-old-library/android/build.gradle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the &lt;code&gt;android {}&lt;/code&gt; block and add the namespace at the top:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;android {
    namespace &quot;com.someoldlibrary&quot;

    compileSdkVersion safeExtGet('compileSdkVersion', 34)

    defaultConfig {
        minSdkVersion safeExtGet('minSdkVersion', 21)
        targetSdkVersion safeExtGet('targetSdkVersion', 34)
    }

    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure the namespace exactly matches the package name from the manifest.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-generate-a-patch&quot;&gt;Step 3: Generate a Patch&lt;/h4&gt;
&lt;p&gt;From your project root, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx patch-package react-native-some-old-library
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a file inside a new &lt;code&gt;patches&lt;/code&gt; directory containing the modification you just made.&lt;/p&gt;
&lt;p&gt;Commit that file to Git.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-4-apply-the-patch-automatically&quot;&gt;Step 4: Apply the Patch Automatically&lt;/h4&gt;
&lt;p&gt;Open &lt;code&gt;package.json&lt;/code&gt; and add a &lt;code&gt;postinstall&lt;/code&gt; script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;expo start&quot;,
    &quot;android&quot;: &quot;expo run:android&quot;,
    &quot;ios&quot;: &quot;expo run:ios&quot;,
    &quot;postinstall&quot;: &quot;patch-package&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now whenever someone runs &lt;code&gt;npm install&lt;/code&gt;—whether it's you, another developer, CI, or EAS Build—the patch gets applied automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;that-s-it&quot;&gt;That's It&lt;/h3&gt;
&lt;p&gt;Clear any caches if needed and run your Android build again.&lt;/p&gt;
&lt;p&gt;The build should now complete normally.&lt;/p&gt;
&lt;p&gt;More importantly, you won't have to keep making the same edit every time &lt;code&gt;node_modules&lt;/code&gt; gets reinstalled.&lt;/p&gt;
&lt;p&gt;If you're working with older React Native dependencies, this is usually the cleanest way to handle AGP 8 namespace issues without waiting for the library maintainer to release an update.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/22158/how-to-fix-namespace-not-specified-gradle-8-error-in-react-native-expo</guid>
<pubDate>Thu, 09 Jul 2026 12:45:41 +0000</pubDate>
</item>
<item>
<title>How to Fix Expo's Cryptic Upgrade Trap: Android Resource Linking Failed (splashscreen_logo not found</title>
<link>https://coderlegion.com/21025/how-expos-cryptic-upgrade-trap-android-resource-linking-failed-splashscreenlogo-found</link>
<description>&lt;h2 id=&quot;how-to-fix-resource-drawable-splashscreen-logo-not-found-after-an-expo-sdk-upgrade&quot;&gt;How to Fix &quot;resource drawable/splashscreen_logo not found&quot; After an Expo SDK Upgrade&lt;/h2&gt;
&lt;p&gt;Upgrading your Expo SDK version to keep up with the latest App Store and Google Play requirements is usually a smooth process—until Gradle decides to punch you in the face with a cryptic asset compilation error.&lt;/p&gt;
&lt;p&gt;If you just kicked off an Android build or ran a prebuild, and your terminal suddenly spit out a wall of red text looking exactly like this, you are not alone:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
&amp;gt; Android resource linking failed
error: resource drawable/splashscreen_logo (aka com.yourcompany.app:drawable/splashscreen_logo) not found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The worst part about this error is that it doesn't give you a file name, a line number, or any mention of Expo.&lt;/p&gt;
&lt;p&gt;It just drops a native Android resource compilation failure and leaves you to figure out the rest.&lt;/p&gt;
&lt;p&gt;Let's break down exactly why this happens during Expo upgrades and how to fix it in two minutes flat.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-this-happens-the-missing-asset-trap&quot;&gt;Why This Happens: The Missing Asset Trap&lt;/h2&gt;
&lt;p&gt;This issue is a classic side effect of how newer Expo SDK versions handle Continuous Native Generation (CNG) via the &lt;code&gt;expo-splash-screen&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;In older versions of Expo, if you only wanted a solid background color for your splash screen, you could get away with just defining &lt;code&gt;backgroundColor&lt;/code&gt; in your config and skipping the logo image entirely.&lt;/p&gt;
&lt;p&gt;The native generator would handle it gracefully.&lt;/p&gt;
&lt;p&gt;However, in newer Expo SDK configurations, the prebuild engine has become much more strict.&lt;/p&gt;
&lt;p&gt;If the &lt;code&gt;image&lt;/code&gt; property is missing from your splash screen configuration, the Expo generator completely skips creating the native Android XML drawable references for the logo.&lt;/p&gt;
&lt;p&gt;When the Android Gradle plugin compiles the app layer, it looks into the generated styles for the splash screen layout, tries to link the &lt;code&gt;splashscreen_logo&lt;/code&gt; asset, finds a blank void, and crashes the entire build.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-to-fix-it-1&quot;&gt;How to Fix It&lt;/h2&gt;
&lt;p&gt;To resolve this, we need to explicitly force the Expo prebuild engine to map a valid drawable resource so Gradle stops complaining.&lt;/p&gt;
&lt;h3 id=&quot;step-1-update-your-app-configuration&quot;&gt;Step 1: Update Your App Configuration&lt;/h3&gt;
&lt;p&gt;Open your &lt;code&gt;app.json&lt;/code&gt; (or &lt;code&gt;app.config.js&lt;/code&gt;) file and locate your &lt;code&gt;plugins&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;Make sure the &lt;code&gt;expo-splash-screen&lt;/code&gt; plugin block contains both a background color and an explicit path to a fallback image asset.&lt;/p&gt;
&lt;p&gt;Even if you don't actually want a logo on your splash screen, you need to provide a placeholder image (like a tiny 1x1 transparent or solid pixel matching your background color) to satisfy the native compiler.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;plugins&quot;: [
  [
    &quot;expo-splash-screen&quot;,
    {
      &quot;backgroundColor&quot;: &quot;#ffffff&quot;,
      &quot;image&quot;: &quot;./assets/splash-icon.png&quot;
    }
  ]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-clear-and-regenerate-native-directories&quot;&gt;Step 2: Clear and Regenerate Native Directories&lt;/h3&gt;
&lt;p&gt;Because native Android build directories heavily cache old configurations, simply changing &lt;code&gt;app.json&lt;/code&gt; isn't always enough to clear out the corrupted Gradle state.&lt;/p&gt;
&lt;p&gt;Run the following commands in your terminal to wipe the slate clean and force a fresh native generation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# If you are testing local native builds:
npx expo prebuild --clean

# Or if you are running a fresh release compilation:
npx expo run:android --variant release --no-build-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the prebuild engine re-runs with the new configuration, it will successfully generate the &lt;code&gt;splashscreen_logo.xml&lt;/code&gt; drawable file inside your native directories, and Gradle will breeze right past the linking stage.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;interactive-reference-amp-live-fix&quot;&gt;Interactive Reference &amp;amp; Live Fix&lt;/h2&gt;
&lt;p&gt;If you want to view a fully verified configuration block or run this code through an interactive debugger to make sure your syntax matches up perfectly, you can check out the public resolution ledger here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;Fix My Error&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-8&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Native Android build failures often look terrifying because the error message points to generated resources rather than the actual configuration mistake.&lt;/p&gt;
&lt;p&gt;In this case, the root cause is usually a missing splash screen image definition after upgrading to a newer Expo SDK version.&lt;/p&gt;
&lt;p&gt;Once you add a valid image path and regenerate your native directories, the build should proceed normally.&lt;/p&gt;
&lt;p&gt;Have you run into any other weird compilation errors while moving your project up to the latest Expo SDK?&lt;/p&gt;
&lt;p&gt;Drop them in the comments below and let's unblock them.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/21025/how-expos-cryptic-upgrade-trap-android-resource-linking-failed-splashscreenlogo-found</guid>
<pubDate>Sat, 20 Jun 2026 12:10:50 +0000</pubDate>
</item>
<item>
<title>Debugging frontend crash and handling circular dependency</title>
<link>https://coderlegion.com/20214/debugging-frontend-crash-and-handling-circular-dependency</link>
<description>&lt;p&gt;A production crash in NeetoCRM was caused by a circular dependency between commons/constants.js and commons/utils.jsx. In development, Vite's native ES module evaluation order masked the issue, but esbuild's production bundling exposed it by placing constants.js code before createColumn was initialized. The fix involved moving createColumn into a standalone module to break the cycle. The post also covers two related findings: how session replay tools like rrweb wrap console methods and mislead DevTools attribution, and how Honeybadger missed the crash entirely because it was initialized inside the React tree rather than before the App chunk loaded. The solution moves Honeybadger.configure() to application.js before mount() runs, ensuring startup failures are captured.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20214/debugging-frontend-crash-and-handling-circular-dependency</guid>
<pubDate>Thu, 11 Jun 2026 01:17:15 +0000</pubDate>
</item>
<item>
<title>🚀 Debuggix – AI That Fixes Your Code in 60 Seconds</title>
<link>https://coderlegion.com/20181/debuggix-ai-that-fixes-your-code-in-60-seconds</link>
<description>&lt;p&gt;Tired of spending hours debugging your code? Debuggix uses 9 powerful security and error-checking engines to automatically detect and fix issues in your code in under a minute. Simply paste your code, let the AI analyze it, and get instant fixes while ensuring security and reliability.&lt;/p&gt;
&lt;p&gt;Key Features:&lt;/p&gt;
&lt;p&gt;Multi-language support – Works with major programming languages.&lt;br&gt;
Security-focused – 9 engines check for vulnerabilities.&lt;br&gt;
Time-saving – Fix bugs in 60 seconds.&lt;br&gt;
AI-powered – Smart suggestions and automated corrections.&lt;/p&gt;
&lt;p&gt;Perfect for developers, freelancers, and teams who want to write secure, bug-free code faster.&lt;/p&gt;
&lt;p&gt;Check it out: Debuggix&lt;/p&gt;
&lt;h2 id=&quot;ai-codeautomation-debugging-developertools-productivity-security&quot;&gt;AI #CodeAutomation #Debugging #DeveloperTools #Productivity #Security&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20181/debuggix-ai-that-fixes-your-code-in-60-seconds</guid>
<pubDate>Wed, 10 Jun 2026 14:28:52 +0000</pubDate>
</item>
<item>
<title>How to Fix Xcode C++ Compiler Errors in React Native (Yoga / glog / constexpr)</title>
<link>https://coderlegion.com/19813/how-to-fix-xcode-c-compiler-errors-in-react-native-yoga-glog-constexpr</link>
<description>&lt;p&gt;You just upgraded your local development environment, updated Xcode, or bumped a minor patch version in your React Native project. You open your terminal, run your standard iOS build command, and instead of a clean bundling process, the terminal throws a massive wall of raw C++ compiler text at you.&lt;/p&gt;
&lt;p&gt;The error logs likely point to internal framework files like &lt;code&gt;Yoga.cpp&lt;/code&gt;, &lt;code&gt;glog&lt;/code&gt;, or the &lt;code&gt;fmt&lt;/code&gt; library, screaming about &lt;code&gt;constexpr&lt;/code&gt; or &lt;code&gt;consteval&lt;/code&gt; issues, or complaining that a specific language standard identifier is missing.&lt;/p&gt;
&lt;p&gt;This is one of the single most frustrating traps in mobile development. Your JavaScript is perfect. Your React components are clean. But your entire build is dead because an underlying C++ compilation standard is fighting your new Xcode tooling.&lt;/p&gt;
&lt;h2 id=&quot;why-this-happens-to-javascript-developers&quot;&gt;Why This Happens to JavaScript Developers&lt;/h2&gt;
&lt;p&gt;React Native relies heavily on a core C++ layout engine called Yoga under the hood. When Apple updates Xcode, they also update the underlying compiler tools (Clang) and shift the default C++ language dialect standard forward (for example, enforcing strict C++20 or C++23 rules).&lt;/p&gt;
&lt;p&gt;If an older version of a library in your &lt;code&gt;node_modules&lt;/code&gt; uses a syntax decoration that the new compiler now considers illegal or deprecated, the native compilation step fails instantly. Because most mobile developers do not actively write pure C++, looking at a raw Clang compiler failure feels like looking at alien code.&lt;/p&gt;
&lt;p&gt;Nuking &lt;code&gt;DerivedData&lt;/code&gt; or running &lt;code&gt;pod install&lt;/code&gt; twenty times will not change the fact that the compiler rules have changed.&lt;/p&gt;
&lt;h2 id=&quot;the-solution-force-the-c-language-standard-backwards&quot;&gt;The Solution: Force the C++ Language Standard Backwards&lt;/h2&gt;
&lt;p&gt;Instead of attempting to manually modify source code deep inside your native &lt;code&gt;node_modules&lt;/code&gt; dependencies (which will just get overwritten the next time you install packages), you can use a CocoaPods post-install hook to force the compiler to accept the specific language dialect your dependencies need to pass the check.&lt;/p&gt;
&lt;p&gt;Open the &lt;code&gt;Podfile&lt;/code&gt; located inside your project's native &lt;code&gt;ios&lt;/code&gt; directory. Scroll down to the bottom where your &lt;code&gt;post_install&lt;/code&gt; loop lives, and inject this compiler flag override block:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # Force the compiler to use the stable C++17 standard for all sub-dependencies
      config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By adding this configuration script, CocoaPods will automatically modify the native build files for every single sub-dependency when you run your installation pipeline, forcing Xcode to evaluate the underlying C++ libraries under a compatible standard framework.&lt;/p&gt;
&lt;h2 id=&quot;clear-the-native-caches-and-rebuild&quot;&gt;Clear the Native Caches and Rebuild&lt;/h2&gt;
&lt;p&gt;Once the project configurations are updated, you must completely destroy the old build artifacts that were compiled under the conflicting settings, or Xcode will continue to throw the same syntax exception.&lt;/p&gt;
&lt;p&gt;Run this quick command chain in your project terminal to wipe the native cache layers and execute a clean compilation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Clear Xcode's local compilation cache
rm -rf ~/Library/Developer/Xcode/DerivedData

# Re-evaluate the Podfile with the new compiler hook
cd ios
pod install --repo-update

# Return to root and boot the iOS simulator
cd ..
npx react-native run-ios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as the native build pipelines evaluate your sub-dependencies through the consistent compiler standard, the cryptic layout exceptions will vanish and your bundle will build cleanly.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19813/how-to-fix-xcode-c-compiler-errors-in-react-native-yoga-glog-constexpr</guid>
<pubDate>Sat, 06 Jun 2026 09:39:12 +0000</pubDate>
</item>
<item>
<title>How to fix 'RNGestureHandlerModule could not be found' in React Native &amp; Expo</title>
<link>https://coderlegion.com/19360/how-to-fix-rngesturehandlermodule-could-not-be-found-in-react-native-expo</link>
<description>&lt;p&gt;Ever run a fresh compilation on a React Native project, change absolutely nothing, and watch your simulator immediately crash with this terminal masterpiece?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ERROR Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'RNGestureHandlerModule' could not be found. Verify that your native modules are linked correctly.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It usually happens right after initializing a navigation library or an interactive UI package that relies on &lt;code&gt;react-native-gesture-handler&lt;/code&gt; under the hood. &lt;/p&gt;
&lt;p&gt;If you are stuck looking at this stack trace right now, here is exactly why it is broken and how to resolve it in two minutes.&lt;/p&gt;
&lt;h4 id=&quot;the-root-cause-javascript-is-floating-alone&quot;&gt;The Root Cause: JavaScript Is Floating Alone&lt;/h4&gt;
&lt;p&gt;When you run standard web frameworks, importing a package updates your node dependency tree and just works. In React Native, complex packages have two distinct layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The JavaScript API layer&lt;/strong&gt; (the code you write in your editor).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Native Architecture layer&lt;/strong&gt; (the actual Swift/Objective-C or Java/Kotlin binaries compiled into the iOS/Android app wrappers).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This error occurs because your Metro bundler successfully loaded the new JavaScript components, but your local native build directory has no idea those new binaries exist yet. The framework is trying to call native routines that simply weren't compiled during your last native build cycle.&lt;/p&gt;
&lt;h4 id=&quot;how-to-fix-it-2&quot;&gt;How to Fix It&lt;/h4&gt;
&lt;h5 id=&quot;1-for-expo-development-builds-most-common-&quot;&gt;1. For Expo Development Builds (Most Common)&lt;/h5&gt;
&lt;p&gt;If you are using Expo and running prebuilds, simply running a standard terminal refresh won't pass the native binary threshold. You need to trigger a full native binary compilation sequence to force Expo to link the missing native modules folder structure into your binary targets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# For Android simulators/devices:
npx expo run:android

# For iOS simulators/devices:
npx expo run:ios
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-for-bare-react-native-projects&quot;&gt;2. For Bare React Native Projects&lt;/h5&gt;
&lt;p&gt;If you are managing your own native folders directly, you need to clear your build cache and explicitly map the cocoapods dependency layer for iOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Clear out your local build artifacts
watchman watch-del-all &amp;amp;&amp;amp; rm -rf node_modules &amp;amp;&amp;amp; npm install

# Re-link native iOS libraries
cd ios &amp;amp;&amp;amp; pod install &amp;amp;&amp;amp; cd ..

# Rebuild your active runtime binary
npx react-native run-ios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the compilation pipeline finishes creating the new application layout, the native module registration bridge hooks up properly, and your navigation screens will load without a hitch.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Hopefully, this saves you from losing an hour down a stack trace rabbit hole today. &lt;/p&gt;
&lt;p&gt;If you run into other cryptic Expo or React Native native errors while building, I put together a quick web diagnostic tool called &lt;a rel=&quot;nofollow&quot; href=&quot;https://fix-my-error.vercel.app&quot;&gt;FixMyError.dev&lt;/a&gt; that contextually reverse-engineers stack traces into human-readable resolutions like this one. There's a free live sandbox on the landing page if you just want to test it out next time your terminal starts throwing errors. &lt;/p&gt;
&lt;p&gt;Back to building!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19360/how-to-fix-rngesturehandlermodule-could-not-be-found-in-react-native-expo</guid>
<pubDate>Sun, 31 May 2026 17:13:39 +0000</pubDate>
</item>
<item>
<title>The Bash $PATH Debugger I Run Whenever I Get &quot;Command Not Found&quot;</title>
<link>https://coderlegion.com/17087/the-bash-path-debugger-i-run-whenever-i-get-command-not-found</link>
<description>&lt;p&gt;Every experienced Linux user has been there: you install something, or move directories around, and suddenly &lt;code&gt;command not found&lt;/code&gt; appears for tools you know are installed.&lt;/p&gt;
&lt;p&gt;You check &lt;code&gt;which&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, echo &lt;code&gt;$PATH&lt;/code&gt;, and still waste 10-20 minutes hunting down the issue. I've done it more times than I care to admit — especially after fresh server setups or when cleaning up dotfiles.&lt;/p&gt;
&lt;p&gt;That's why I built (and now constantly use) a &lt;strong&gt;Bash $PATH Debugger&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;paste-your-path-and-get-instant-diagnosis&quot;&gt;Paste your $PATH and get instant diagnosis&lt;/h4&gt;
&lt;p&gt;Here's how it works in practice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy your current PATH: &lt;code&gt;echo $PATH&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Paste it into the tool&lt;/li&gt;
&lt;li&gt;Get a full report + fixed version in seconds&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Try it here:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/tools/path-debugger.html&quot;&gt;Bash $PATH Debugger&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;what-the-tool-does&quot;&gt;What the tool does&lt;/h4&gt;
&lt;p&gt;The debugger takes your raw &lt;code&gt;$PATH&lt;/code&gt; string and analyzes every directory with these checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Existence&lt;/strong&gt;: Does the directory actually exist on the filesystem?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duplicates&lt;/strong&gt;: Are you wasting lookup time with repeated entries?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permissions&lt;/strong&gt;: Are any directories missing execute permissions for your user?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Order&lt;/strong&gt;: Shows the exact search priority (first match wins)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean output&lt;/strong&gt;: Generates a cleaned, deduplicated &lt;code&gt;export PATH=...&lt;/code&gt; line ready for &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.profile&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It even highlights common problems like missing &lt;code&gt;~/.local/bin&lt;/code&gt;, collapsed entries after moves, or directories that exist but aren't in your current session.&lt;/p&gt;
&lt;h4 id=&quot;why-this-tool-saves-so-much-time&quot;&gt;Why this tool saves so much time&lt;/h4&gt;
&lt;p&gt;&quot;command not found&quot; (exit code 127) is one of the most searched Bash errors online. Yet most resources just tell you to &lt;code&gt;export PATH=...&lt;/code&gt; manually without diagnosing &lt;em&gt;why&lt;/em&gt; it broke.&lt;/p&gt;
&lt;p&gt;This tool does the diagnosis for you. It's especially powerful on fresh VPS setups, after migrating user accounts, or when working with Docker/container environments where PATH behaves differently.&lt;/p&gt;
&lt;h4 id=&quot;real-world-use-cases&quot;&gt;Real-world use cases&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;After a &lt;code&gt;apt install&lt;/code&gt; or &lt;code&gt;brew install&lt;/code&gt; that doesn't add itself to PATH properly&lt;/li&gt;
&lt;li&gt;Debugging why a script works in one terminal but not another&lt;/li&gt;
&lt;li&gt;Cleaning up an overloaded PATH that accumulated junk over years&lt;/li&gt;
&lt;li&gt;Preparing a clean minimal PATH for production servers&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;pro-tip-test-on-a-clean-vps&quot;&gt;Pro tip: Test on a clean VPS&lt;/h4&gt;
&lt;p&gt;One of my favorite workflows is spinning up a fresh $4-5/mo VPS, installing my usual tools, running them through the debugger, and copying the optimized PATH into my base server image.&lt;/p&gt;
&lt;h4 id=&quot;common-variations-amp-advanced-usage&quot;&gt;Common variations &amp;amp; advanced usage&lt;/h4&gt;
&lt;p&gt;The tool also supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Relative path expansion&lt;/li&gt;
&lt;li&gt;Handling of &lt;code&gt;~&lt;/code&gt; and environment variables in PATH entries&lt;/li&gt;
&lt;li&gt;Export formats for different shells (bash, zsh, fish compatibility notes)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Full interactive tool with live diagnosis, cleaned PATH generator, and detailed explanations:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/tools/path-debugger.html&quot;&gt;https://bashsnippets.xyz/tools/path-debugger.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Paste your PATH. Fix the problem. Move on.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/17087/the-bash-path-debugger-i-run-whenever-i-get-command-not-found</guid>
<pubDate>Fri, 08 May 2026 18:09:24 +0000</pubDate>
</item>
<item>
<title>Optimizing Flutter Apps: A Deep Dive into Dart DevTools</title>
<link>https://coderlegion.com/17045/optimizing-flutter-apps-a-deep-dive-into-dart-devtools</link>
<description>&lt;p&gt;Most Flutter developers know how to build apps. Far fewer know how to build &lt;em&gt;fast&lt;/em&gt; ones. The gap between a good app and a great one often isn't in the features. It's in the frames dropped, the memory leaked, and the milliseconds lost.&lt;/p&gt;
&lt;p&gt;Dart DevTools is your toolkit for closing that gap. This guide walks through each of its major panels, what they're good for, and how to actually use them on a real app.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Before you start:&lt;/strong&gt; Always profile in &lt;strong&gt;profile mode&lt;/strong&gt;, not debug mode. Debug mode runs with extra assertions and overhead that will skew every reading you take.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flutter run --profile
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-flutter-inspector-the-widget-tree-x-ray&quot;&gt;1. Flutter Inspector — The Widget Tree X-Ray&lt;/h3&gt;
&lt;p&gt;The Flutter Inspector gives you a live, interactive view of your widget tree. Think of it as browser DevTools, but for your UI hierarchy. Here are the four toggles you'll use most.&lt;/p&gt;
&lt;h4 id=&quot;slow-animations&quot;&gt;Slow Animations&lt;/h4&gt;
&lt;p&gt;Complex animations can be impossible to debug at full speed. Toggling &lt;strong&gt;Slow Animations&lt;/strong&gt; in the Inspector, or setting this in code, drops them to 20% speed so you can catch glitches frame by frame.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;import 'package:flutter/scheduler.dart';

void main() {
  timeDilation = 5.0; // 5× slower
  runApp(const MyApp());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;guidelines-amp-baselines&quot;&gt;Guidelines &amp;amp; Baselines&lt;/h4&gt;
&lt;p&gt;Enables visual overlays that draw:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scroll direction indicators on scrollable widgets&lt;/li&gt;
&lt;li&gt;Widget boundaries (render object outlines)&lt;/li&gt;
&lt;li&gt;Text baselines to catch misaligned typography&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is particularly useful when you suspect a layout issue but cannot pinpoint which widget is the culprit.&lt;/p&gt;
&lt;h4 id=&quot;highlight-repaints&quot;&gt;Highlight Repaints&lt;/h4&gt;
&lt;div align=&quot;center&quot;&gt;
  &lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=12462702801371394349&quot; width=&quot;45%&quot; style=&quot;border-radius:50px&quot; alt=&quot;image&quot;&gt;
&lt;p&gt;  &lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1234491430867038178&quot; width=&quot;44%&quot; style=&quot;border-radius:50px&quot; alt=&quot;image&quot;&gt;&lt;br&gt;
&lt;/p&gt;&lt;/div&gt;&lt;br&gt;
Every time a widget repaints, it flashes a continuous stream of color. If your entire screen is flashing because a small loading spinner is animating, you have a problem.
&lt;p&gt;I once traced a scrolling jank issue to a blinking typing indicator that was forcing an entire chat screen to repaint every frame.&lt;/p&gt;
&lt;p&gt;The fix is a &lt;code&gt;RepaintBoundary&lt;/code&gt;. It tells Flutter to isolate that subtree into its own render layer, so its repaints do not cascade upward.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;RepaintBoundary(
  child: MyAnimatedWidget(),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this around widgets that animate independently, such as loaders, progress bars, and live counters.&lt;/p&gt;
&lt;h4 id=&quot;highlight-oversized-images&quot;&gt;Highlight Oversized Images&lt;/h4&gt;
&lt;p&gt;If an image is decoded at 2000×2000px but displayed at 100×100px, you're wasting memory on 39,900 pixels that will never be seen. This toggle shows them as negative and rotates them as shown in the image&lt;br&gt;
&lt;/p&gt;&lt;div align=&quot;center&quot;&gt;&lt;br&gt;
  &lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13324385877305745567&quot; width=&quot;45%&quot; style=&quot;border-radius:50px&quot; alt=&quot;image&quot;&gt;&lt;br&gt;
&lt;/div&gt;
&lt;p&gt;The fix is straightforward. Constrain decoding at the source:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;Image.network(
  'https://example.com/large-image.jpg',
  cacheWidth: 200,
  cacheHeight: 200,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flutter will downsample the image before caching it, significantly reducing memory pressure on image-heavy screens.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-app-size-analysis-know-what-you-re-shipping&quot;&gt;2. App Size Analysis — Know What You're Shipping&lt;/h3&gt;
&lt;p&gt;A bloated app takes longer to download, longer to install, and gives a worse first impression before the user has even launched it. The &lt;code&gt;--analyze-size&lt;/code&gt; flag gives you a breakdown of exactly what's eating into your bundle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For Android&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flutter build apk --analyze-size
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;For iOS&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flutter build ipa --analyze-size
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This outputs a JSON file that you import directly into DevTools. Once loaded, you get two views:&lt;/p&gt;
&lt;h4 id=&quot;dominator-tree&quot;&gt;Dominator Tree&lt;/h4&gt;
&lt;p&gt;Shows which packages are responsible for the largest chunks of your app size. If a utility package you barely use is claiming 3MB, it is worth reconsidering.&lt;/p&gt;
&lt;h4 id=&quot;call-graph&quot;&gt;Call Graph&lt;/h4&gt;
&lt;p&gt;Maps how your dependencies import each other. This is how you find transitive dependencies you did not know you had. A package pulls another package, which pulls another, each silently adding weight.&lt;/p&gt;
&lt;p&gt;Size is a feature.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8905883950125565687&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-performance-amp-jank-tracking-chasing-smooth-frames&quot;&gt;3. Performance &amp;amp; Jank Tracking — Chasing Smooth Frames&lt;/h3&gt;
&lt;p&gt;The target is 60 frames per second. That gives each frame exactly &lt;strong&gt;16.6 milliseconds&lt;/strong&gt; to build and render. Any frame that exceeds this budget is called a &lt;strong&gt;jank&lt;/strong&gt;, and users feel every one of them, even if they cannot name what's wrong.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Performance&lt;/strong&gt; tab lets you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Record a session of real app interactions&lt;/li&gt;
&lt;li&gt;View a flame chart of frame build times&lt;/li&gt;
&lt;li&gt;Click into expensive frames to see exactly which build, layout, or paint call consumed the most time&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key is to record &lt;em&gt;specific interactions&lt;/em&gt; rather than letting it run indefinitely. Navigate to the screen you suspect is slow, hit record, reproduce the issue, then stop. A focused recording is far easier to read than five minutes of noise.&lt;/p&gt;
&lt;p&gt;Look for the red frames in the timeline. Those are your jank frames. Drill into them, and you'll usually find one of three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An expensive &lt;code&gt;build()&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;An unnecessary full-tree rebuild&lt;/li&gt;
&lt;li&gt;A synchronous operation blocking the main thread&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Heavy JSON parsing, large computations, or image processing should not run on the UI isolate. Move them to another isolate using &lt;code&gt;compute()&lt;/code&gt; whenever possible.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;final result = await compute(parseLargeJson, jsonString);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;section-1&quot;&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=4199897461815345682&quot; alt=&quot;&quot;&gt;&lt;/h3&gt;
&lt;h3 id=&quot;4-network-amp-cpu-profiling-under-the-hood&quot;&gt;4. Network &amp;amp; CPU Profiling — Under the Hood&lt;/h3&gt;
&lt;h4 id=&quot;network-tab&quot;&gt;Network Tab&lt;/h4&gt;
&lt;p&gt;If you've ever tried to debug an API issue by reading through a wall of &lt;code&gt;flutter: {...}&lt;/code&gt; in the debug console, the Network tab will feel like a revelation.&lt;/p&gt;
&lt;p&gt;It captures every HTTP request your app makes and displays:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full request and response headers&lt;/li&gt;
&lt;li&gt;Response body and status codes&lt;/li&gt;
&lt;li&gt;Request timing and duration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No more print-statement archaeology. You can see at a glance if a request is failing, slow, or returning unexpected data.&lt;/p&gt;
&lt;h4 id=&quot;cpu-profiler&quot;&gt;CPU Profiler&lt;/h4&gt;
&lt;p&gt;The CPU Profiler records a flame chart of method calls while your app runs. When something feels sluggish but the Performance tab does not show obvious jank, this is where you look.&lt;/p&gt;
&lt;p&gt;It is especially good for catching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expensive operations accidentally running on the main isolate&lt;/li&gt;
&lt;li&gt;Framework callbacks firing more often than expected&lt;/li&gt;
&lt;li&gt;Third-party packages with surprisingly heavy footprints&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-memory-amp-debugger-stability-under-pressure&quot;&gt;5. Memory &amp;amp; Debugger — Stability Under Pressure&lt;/h3&gt;
&lt;h4 id=&quot;memory-tab&quot;&gt;Memory Tab&lt;/h4&gt;
&lt;p&gt;Memory leaks are silent killers. Your app might run perfectly in testing and degrade over time in production as memory accumulates and is never released.&lt;/p&gt;
&lt;p&gt;The most common culprit in Flutter is forgetting to &lt;code&gt;dispose()&lt;/code&gt; objects that hold onto resources.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;class MyWidget extends StatefulWidget { ... }

class _MyWidgetState extends State&amp;lt;MyWidget&amp;gt;
    with SingleTickerProviderStateMixin {

  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Memory tab shows a real-time chart of heap allocation. If memory climbs steadily as you navigate through your app and never comes back down, you likely have a leak.&lt;/p&gt;
&lt;p&gt;Reproduce the navigation repeatedly and watch the graph. A healthy app's memory should stabilize over time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7694226450075472459&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;debugger&quot;&gt;Debugger&lt;/h4&gt;
&lt;p&gt;The built-in debugger supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Breakpoints&lt;/li&gt;
&lt;li&gt;Step-through execution&lt;/li&gt;
&lt;li&gt;Variable inspection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All without leaving DevTools.&lt;/p&gt;
&lt;p&gt;This is useful for tracking down logic bugs that only appear in certain states, or for understanding a complex call chain without littering your codebase with &lt;code&gt;print()&lt;/code&gt; calls.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-principles-to-take-away&quot;&gt;Key Principles to Take Away&lt;/h3&gt;
&lt;h4 id=&quot;profile-mode-is-non-negotiable&quot;&gt;Profile Mode Is Non-Negotiable&lt;/h4&gt;
&lt;p&gt;Debug mode adds overhead that makes everything look slower than it is. Measure performance in profile mode, always.&lt;/p&gt;
&lt;h4 id=&quot;do-not-optimize-blindly&quot;&gt;Do Not Optimize Blindly&lt;/h4&gt;
&lt;p&gt;Reach for DevTools when you observe a real problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A user complaint&lt;/li&gt;
&lt;li&gt;A visible stutter&lt;/li&gt;
&lt;li&gt;A reported crash&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Premature optimization creates complexity without measurable benefit.&lt;/p&gt;
&lt;h4 id=&quot;older-devices-are-your-benchmark&quot;&gt;Older Devices Are Your Benchmark&lt;/h4&gt;
&lt;p&gt;An app that runs smoothly on a flagship device might choke on a three-year-old mid-range phone. Test on the weakest hardware your users are likely to have.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Dart DevTools removes the guesswork from Flutter performance.&lt;/p&gt;
&lt;p&gt;Each panel answers a specific category of question:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What's rendering&lt;/li&gt;
&lt;li&gt;What's too big&lt;/li&gt;
&lt;li&gt;What's too slow&lt;/li&gt;
&lt;li&gt;What's leaking&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use them together, and you'll spend less time wondering why your app feels sluggish and more time fixing it.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;strong&gt;Aswin Gopinathan&lt;/strong&gt; for this wonderful workshop, from which I got the knowledge for this blog. Do check it out on &lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/dTNMxeIfS-o?si=GttEhpSuIeRD8s8i&quot;&gt;Youtube&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17045/optimizing-flutter-apps-a-deep-dive-into-dart-devtools</guid>
<pubDate>Fri, 08 May 2026 09:59:05 +0000</pubDate>
</item>
</channel>
</rss>