<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: cynthia wahome</title>
    <description>The latest articles on DEV Community by cynthia wahome (@cynthizo).</description>
    <link>https://dev.to/cynthizo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1775793%2F1e9560fe-a2de-4cc0-8d27-2346c18af9aa.png</url>
      <title>DEV Community: cynthia wahome</title>
      <link>https://dev.to/cynthizo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cynthizo"/>
    <language>en</language>
    <item>
      <title>The Supabase Gotchas Nobody Warns You About (Until You Hit Them)</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:23:24 +0000</pubDate>
      <link>https://dev.to/cynthizo/the-supabase-gotchas-nobody-warns-you-about-until-you-hit-them-2a7g</link>
      <guid>https://dev.to/cynthizo/the-supabase-gotchas-nobody-warns-you-about-until-you-hit-them-2a7g</guid>
      <description>&lt;p&gt;Supabase has some of the best developer experience (DX) in the BaaS space right now. DX is shorthand for how pleasant a tool is to work with — the docs, the dashboard, the auto-generated APIs, the speed at which you go from zero to something working. On all of those: Supabase is excellent.&lt;/p&gt;

&lt;p&gt;But excellent DX has a shadow side. When a platform abstracts things this smoothly, it's easy to forget what's running underneath. And what's running underneath Supabase is &lt;strong&gt;PostgreSQL&lt;/strong&gt; — a powerful, strict, battle-tested database that has its own permission system, its own rules, and absolutely no obligation to care how clean your dashboard looks.&lt;/p&gt;

&lt;p&gt;These are the two things that will catch you — usually at the same time, usually showing the same symptom.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Symptom
&lt;/h2&gt;

&lt;p&gt;You've set up auth. Users sign up, verify their email, try to log in — and your app shows something like a "pending approval" or "no role assigned" screen even though everything completed correctly.&lt;/p&gt;

&lt;p&gt;Your trigger is firing. Your RLS policies are in place. The Supabase dashboard looks fine.&lt;/p&gt;

&lt;p&gt;Nothing is broken. Nothing is obviously wrong.&lt;/p&gt;

&lt;p&gt;This is the trap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gotcha 1: The GRANT That Nobody Told You About
&lt;/h2&gt;

&lt;p&gt;This is the most common question in the Supabase Discord and it's easy to see why — it violates every reasonable expectation.&lt;/p&gt;

&lt;p&gt;When you enable Row Level Security (RLS) on a table in Supabase, you create &lt;strong&gt;policies&lt;/strong&gt; that define who can do what with the rows. A policy might say: "authenticated users can read their own rows." Logical. Clear.&lt;/p&gt;

&lt;p&gt;What the dashboard doesn't shout at you is that &lt;strong&gt;RLS policies and table permissions are two entirely separate systems in PostgreSQL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the mental model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it controls&lt;/th&gt;
&lt;th&gt;Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GRANT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can this role access the table at all?&lt;/td&gt;
&lt;td&gt;The keycard that opens the building&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RLS Policy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Which rows can this role see?&lt;/td&gt;
&lt;td&gt;Which floors that keycard can reach&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without a GRANT, the keycard doesn't work. PostgreSQL's PostgREST layer turns you away at the door before RLS even runs. You can have perfectly written policies and they will never be evaluated — because the role was never let in to begin with.&lt;/p&gt;

&lt;p&gt;This is why you can get &lt;code&gt;permission denied for table user_roles&lt;/code&gt; even with the &lt;strong&gt;service_role key&lt;/strong&gt; — the key that's supposed to bypass everything. Without explicit GRANTs, "supposed to bypass everything" turns out to have an asterisk.&lt;/p&gt;

&lt;p&gt;The fix is two lines that most Supabase tutorials never show you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_roles&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;service_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;service_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Two lines of SQL that most migration guides don't include.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Migration Template You Should Steal
&lt;/h2&gt;

&lt;p&gt;Every time you create a table in Supabase, this is the full pattern. Save it as a GitHub Gist. Tattoo it somewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 1. Create the table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. Enable RLS&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 3. Write your policies&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"authenticated_can_select"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 4. THE PART EVERYONE FORGETS&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;service_role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_table&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;anon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 is not optional. It's not an edge case. It is required every single time, and the dashboard will not remind you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 60-Second Diagnostic
&lt;/h2&gt;

&lt;p&gt;Hit &lt;code&gt;permission denied&lt;/code&gt; and not sure why? Run these three queries in the Supabase SQL editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Is RLS actually enabled?&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relrowsecurity&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your_table_name'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Do your policies exist?&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;policyname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;roles&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_policies&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your_table_name'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Do GRANTs exist? (this is usually the missing one)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;grantee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;privilege_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_privileges&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your_table_name'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the third query returns nothing for &lt;code&gt;service_role&lt;/code&gt; or &lt;code&gt;authenticated&lt;/code&gt; — you found it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gotcha 2: The Race Condition Nobody Mentions
&lt;/h2&gt;

&lt;p&gt;This one is subtler and it shows up as a UI flicker — your app briefly flashes an "account pending" or "access denied" screen for a user who is fully verified and approved.&lt;/p&gt;

&lt;p&gt;It's not a database problem. It's a timing problem.&lt;/p&gt;

&lt;p&gt;When a user logs in, your frontend is doing at least two async things at once: confirming the auth session exists, and fetching the user's role from the database. If your UI renders before the role fetch completes, it sees &lt;code&gt;role = null&lt;/code&gt; for a split second and acts accordingly — showing whatever your "no role" fallback state is.&lt;/p&gt;

&lt;p&gt;The common mistake is initialising the loading state as &lt;code&gt;false&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is the trap&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;roleLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRoleLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;false&lt;/code&gt; means "done loading." Your UI reads that and renders immediately, before the role has arrived.&lt;/p&gt;

&lt;p&gt;The fix is using &lt;code&gt;null&lt;/code&gt; as the initial state instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// null = "I genuinely don't know yet — don't render anything"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;roleLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRoleLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;null&lt;/code&gt; means "ask me later." Your UI holds. The role arrives. The correct screen renders. No flicker.&lt;/p&gt;

&lt;p&gt;It's a one-character change — &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt; — that's the difference between a UI that lies to users for 200ms and one that waits until it knows the truth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Happens With BaaS Platforms Specifically
&lt;/h2&gt;

&lt;p&gt;Both of these issues share a root cause: &lt;strong&gt;Supabase abstracts so much so well that it's easy to stop thinking about the layer underneath.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The dashboard gives you toggles. The client library gives you clean methods. The auto-generated APIs give you something that feels like it's handling everything. And mostly it is — until it isn't, and the failure is silent, and the symptom looks identical whether the problem is a missing GRANT or a race condition or something else entirely.&lt;/p&gt;

&lt;p&gt;This isn't a criticism. The abstraction is genuinely the point, and it's genuinely good. But every abstraction has a floor. With Supabase, the floor is PostgreSQL — and PostgreSQL has opinions.&lt;/p&gt;

&lt;p&gt;The moment you remember that Supabase is PostgreSQL with excellent packaging, a lot of otherwise mysterious behaviour starts making sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pre-Launch Auth Checklist
&lt;/h2&gt;

&lt;p&gt;Before you ship any Supabase auth flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every table has &lt;strong&gt;both&lt;/strong&gt; RLS policies &lt;strong&gt;and&lt;/strong&gt; GRANT statements&lt;/li&gt;
&lt;li&gt;[ ] Role/loading state initialises as &lt;code&gt;null&lt;/code&gt;, not &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Tested clean: run &lt;code&gt;npx supabase db reset --local&lt;/code&gt; from scratch at least once&lt;/li&gt;
&lt;li&gt;[ ] Tested with both &lt;code&gt;anon&lt;/code&gt; key and &lt;code&gt;service_role&lt;/code&gt; key separately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That third one matters more than it looks. If you've only ever run your app against a database that was built incrementally and never torn down, you haven't found all your migration gaps yet. A clean reset is the smoke detector.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Hit a Supabase gotcha that this doesn't cover?&lt;/strong&gt; Drop it in the comments — these things rarely travel alone. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Backend engineer. I write about the bugs I actually hit. Portfolio: &lt;a href="https://cycy.is-a.dev" rel="noopener noreferrer"&gt;cycy.is-a.dev&lt;/a&gt; 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Usipoziba Ufa, Utajenga Ukuta — On Technical Debt and the Discipline to Fix It</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Mon, 16 Mar 2026 11:59:47 +0000</pubDate>
      <link>https://dev.to/cynthizo/usipoziba-ufa-utajenga-ukuta-on-technical-debt-and-the-discipline-to-fix-it-2h1c</link>
      <guid>https://dev.to/cynthizo/usipoziba-ufa-utajenga-ukuta-on-technical-debt-and-the-discipline-to-fix-it-2h1c</guid>
      <description>&lt;p&gt;There's a Swahili proverb that lives in my head rent-free as a developer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Usipoziba ufa, utajenga ukuta.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;If you don't seal the crack, you'll rebuild the wall.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was recently doing a technical review of a school management system — a real, live, production codebase built by a team moving fast and shipping features. The kind of project where prototyping decisions become permanent ones, and technical debt quietly accumulates in the walls.&lt;/p&gt;

&lt;p&gt;I could have done the minimum. Fixed what was on the ticket, shipped, moved on. Instead I did what I always do — I kept pulling the thread.&lt;/p&gt;

&lt;p&gt;Here's what I found, and the first principles behind every fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Context (Because You Deserve It)
&lt;/h2&gt;

&lt;p&gt;This was a &lt;strong&gt;PHP/Laravel&lt;/strong&gt; backend — a language I was relatively new to at the time. &lt;em&gt;Laravel&lt;/em&gt; is a popular PHP web framework, and &lt;em&gt;Tinker&lt;/em&gt; is its interactive console, like a REPL you can use to query and manipulate the database directly without building a UI. Think of it as a surgical tool for backend diagnosis.&lt;/p&gt;

&lt;p&gt;The system managed multiple schools on one platform (multi-tenancy), had just added a sales agent referral program, and needed a security and UX audit before the next release.&lt;/p&gt;

&lt;p&gt;I wasn't here to judge the original code. Prototyping leaves debt — that's not incompetence, that's software. My job was to leave it better than I found it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Screenshots and PR references: [link to PRs here when publishing]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdghd2ydibkrtsmqv3rg5.png" alt="Closed PR's CynthiaWahome"&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3rzsv3m0gztidwfj9s8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3rzsv3m0gztidwfj9s8.png" alt="Opened PR's CynthiaWahome"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Seal the Crack Before You Need a New Wall
&lt;/h2&gt;

&lt;p&gt;The most critical find: a single helper method with a dangerous fallback.&lt;/p&gt;

&lt;p&gt;The system was supposed to identify which sales agent was making a request by checking their login session. Correct. But if that check failed — instead of stopping — it fell back to reading an &lt;code&gt;agent_id&lt;/code&gt; value straight from the URL.&lt;/p&gt;

&lt;p&gt;Anyone. Any authenticated user. Could type &lt;code&gt;?agent_id=someone-elses-id&lt;/code&gt; in the URL and gain full access to that agent's dashboard, commissions, and payout history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix was one line.&lt;/strong&gt; But the lesson is about what happens when you don't fix it: a crack becomes a wall you eventually have to rebuild entirely — after the breach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; &lt;em&gt;User input is untrusted. Always.&lt;/em&gt; URL parameters, form fields, headers, cookies — all of it can be faked. Never use client-supplied data to make an authorisation decision. This is not a PHP principle. This is not a Laravel principle. It is a law of the internet.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Never Break the Architecture to Solve One Edge Case
&lt;/h2&gt;

&lt;p&gt;This was the most architectural moment of the whole review.&lt;/p&gt;

&lt;p&gt;The system needed a Global Super Admin — someone who could see all agents across all schools. Problem: every user in this system is physically tied to a specific school in the database. That's the security contract. And it's enforced at the database level, not just in code.&lt;/p&gt;

&lt;p&gt;The tempting fix: make the &lt;code&gt;school_id&lt;/code&gt; column nullable. One migration, problem solved.&lt;/p&gt;

&lt;p&gt;I almost did it. Then I stopped.&lt;/p&gt;

&lt;p&gt;Making that column nullable wouldn't just solve this one case — it would quietly weaken the security guarantee for &lt;em&gt;every&lt;/em&gt; school on the platform. The whole point of the design was that cross-school data leaks are structurally impossible. One nullable exception and that guarantee has a crack in it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The actual solution:&lt;/strong&gt; create a real management entity — "Platform HQ" — and make the Super Admin belong to it. The architecture stays intact. The admin has a valid home in the system. No nullable columns. No broken contracts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Own the strongest room inside the walls. Don't remove the walls.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This transcends PHP, Laravel, any framework. Whenever you're about to modify a database schema to accommodate a single edge case — pause. Ask whether the edge case can be made to fit the existing contract instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Guard the Exit, Not the Entrance
&lt;/h2&gt;

&lt;p&gt;A product decision dressed as a security question: should agents who sign up via Google OAuth be blocked from the dashboard until an admin manually approves them?&lt;/p&gt;

&lt;p&gt;The security case for blocking: obvious. Unverified users shouldn't see sensitive data.&lt;/p&gt;

&lt;p&gt;The product case against: onboarding friction at the highest-intent moment is how you lose users. Someone who just signed up via Google and immediately hits a wall will leave. You've completed their registration and immediately punished them for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution: guard the exit, not the entrance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let them in. Let them see the dashboard. Let them understand the product and start generating referrals. But the payout service — where money actually moves — requires approval before it runs.&lt;/p&gt;

&lt;p&gt;This is applicable everywhere a payment or financial feature sits behind a registration flow. Google OAuth (or any SSO) is a legitimate, convenient way to onboard users. The mistake is assuming that authentication and authorisation are the same thing. They're not. Authentication says &lt;em&gt;who you are&lt;/em&gt;. Authorisation says &lt;em&gt;what you're allowed to do&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Lock the vault. Leave the lobby open.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Bug That Only Existed in Production
&lt;/h2&gt;

&lt;p&gt;A "Ghost Object" bug — one of the more disorienting things you'll encounter.&lt;/p&gt;

&lt;p&gt;The dashboard "Approve Agent" button returned an error saying the agent wasn't in the right status. I checked directly in Tinker — the status was correct. So why was the controller seeing null?&lt;/p&gt;

&lt;p&gt;Diagnosis via logs showed the entire agent object was empty. The server received the ID from the URL but never fetched the actual record.&lt;/p&gt;

&lt;p&gt;Root cause: the admin routes were wired up manually in a way that bypassed the standard API middleware. &lt;em&gt;Route Model Binding&lt;/em&gt; — the feature that automatically resolves URL parameters into database records — only runs inside that middleware. Without it, the server got an ID and did nothing with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; &lt;em&gt;"Works in local tooling" and "works in the API" are not the same test.&lt;/em&gt; Your REPL, your console, your local scripts — they run in a different context than your actual request lifecycle. When something works in one and fails in the other, look at what the request pipeline does that your tooling skips.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Harden Your Logic Against Its Environment
&lt;/h2&gt;

&lt;p&gt;This one is underrated. We added &lt;code&gt;trim()&lt;/code&gt; and &lt;code&gt;strtolower()&lt;/code&gt; to status comparisons in the data model — not because users were submitting messy input, but because different database engines handle string comparison differently.&lt;/p&gt;

&lt;p&gt;Postgres is strict. MariaDB can be inconsistent about trailing whitespace and case sensitivity depending on configuration. An internal state machine that depends on string equality needs to be immune to the floor beneath it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; &lt;em&gt;Write code that's correct regardless of the environment it runs in.&lt;/em&gt; Defensive logic isn't paranoia. It's acknowledging that you don't control every variable between your code and the database driver.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Came For
&lt;/h2&gt;

&lt;p&gt;I came in to add some UI banners. I left having:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Closed a privilege escalation hole&lt;/li&gt;
&lt;li&gt;Preserved a multi-tenancy security contract that almost got weakened&lt;/li&gt;
&lt;li&gt;Fixed a ghost object caused by misconfigured middleware&lt;/li&gt;
&lt;li&gt;Moved an auth check from the wrong door to the right door&lt;/li&gt;
&lt;li&gt;Hardened string comparisons against database inconsistency&lt;/li&gt;
&lt;li&gt;Opened issues, flagged technical debt, and added milestones so none of it gets lost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters. I could have fixed my ticket and left. But I logged everything else I found — as issues, as notes, as future work. Because debt you've seen and documented is manageable. Debt you've seen and ignored is a wall waiting to be rebuilt.&lt;/p&gt;




&lt;p&gt;I'm naturally curious. I'm not afraid of bugs — they're how I learn. And I genuinely enjoy taking a codebase that's been moving fast and making it move more safely.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/Cyfamod-Technologies" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F177118475%3Fs%3D280%26v%3D4" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/Cyfamod-Technologies" rel="noopener noreferrer" class="c-link"&gt;
            Cyfamod Technologies · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Cyfamod Technologies has 8 repositories available. Follow their code on GitHub.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;The language was PHP. The principles aren't.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Usipoziba ufa, utajenga ukuta.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Seal the crack now. Or rebuild the wall later.&lt;/p&gt;




&lt;p&gt;Backend engineer. I write about systems, architecture, and what I learn by poking at things. Portfolio: cycy.is-a.dev 🚀&lt;/p&gt;

</description>
      <category>security</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>It Ran Fine in Production. Then I Wrote the Tests.</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Thu, 12 Mar 2026 21:01:37 +0000</pubDate>
      <link>https://dev.to/cynthizo/it-ran-fine-in-production-then-i-wrote-the-tests-1kif</link>
      <guid>https://dev.to/cynthizo/it-ran-fine-in-production-then-i-wrote-the-tests-1kif</guid>
      <description>&lt;p&gt;The app worked. Users could log in. Data was saving. Nobody was on fire.&lt;/p&gt;

&lt;p&gt;So naturally, I went looking for the fire.&lt;/p&gt;

&lt;p&gt;I was asked to audit a new &lt;strong&gt;Agent Referral&lt;/strong&gt; feature on a school management system — agents refer schools, earn commissions, the usual. No brief. No checklist. Just &lt;em&gt;"make sure it's fine."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It was not fine. Nothing was visibly broken, but the architecture had been quietly making peace with some genuinely bad decisions. Everything held together by vibes and accumulated runtime state.&lt;/p&gt;

&lt;p&gt;Here's what I found — and why it matters whether you're writing PHP, Python, Go, or whatever language you've convinced yourself won't betray you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Back Door That Was Literally Labelled
&lt;/h2&gt;

&lt;p&gt;There was a helper method — &lt;code&gt;resolveAgent()&lt;/code&gt; — whose job was to figure out which agent was logged in. Two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the authenticated user is an Agent ✅&lt;/li&gt;
&lt;li&gt;If not — check if the URL has an &lt;code&gt;agent_id&lt;/code&gt; parameter, and if so, load that agent from the database ❌&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 is called &lt;strong&gt;Privilege Escalation&lt;/strong&gt;. Any authenticated user — a school admin, a teacher, literally anyone — could type &lt;code&gt;?agent_id=some-uuid&lt;/code&gt; into the URL and gain full access to that agent's dashboard, commissions, and payout history.&lt;/p&gt;

&lt;p&gt;The fix was one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're an agent or you get nothing. No fallbacks. No trusting what someone typed in a URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; &lt;em&gt;Never trust the client.&lt;/em&gt; Anything arriving from a browser — URL params, form fields, headers, cookies — can be faked. Assume it is. This isn't a PHP rule or a Laravel rule. It's a law of the internet, as immovable as gravity and significantly less forgiving.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Database That Only Looked Stable
&lt;/h2&gt;

&lt;p&gt;To prove my fix worked, I wrote tests. Ran them. The whole suite detonated before a single test executed — the database couldn't rebuild itself from the migration files.&lt;/p&gt;

&lt;p&gt;Two issues:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue 1:&lt;/strong&gt; A migration tried to set a default value on a &lt;code&gt;TEXT&lt;/code&gt; column. &lt;code&gt;TEXT&lt;/code&gt; is unbounded — think of it as an essay answer sheet with no word limit. The database's response was essentially &lt;em&gt;"I'm not pre-filling every essay box. That's expensive and I refuse."&lt;/em&gt; MariaDB won't allow it. MySQL might let it slide depending on config. They're like twins — 99% identical, except MariaDB read the rulebook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue 2:&lt;/strong&gt; A composite primary key included a &lt;code&gt;nullable&lt;/code&gt; column. A primary key is the database's ID badge for a row. An ID badge that might be blank can't identify anything. The database said no. Correctly.&lt;/p&gt;

&lt;p&gt;The wild part? The production app was running fine. The database had been built incrementally over months, never torn down. My tests used &lt;code&gt;RefreshDatabase&lt;/code&gt; — which destroys everything and rebuilds from scratch every run. That clean slate exposed every shortcut that had been quietly accumulating.&lt;/p&gt;

&lt;p&gt;It's like a building that looks solid because nobody's pulled up the floorboards in years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; &lt;em&gt;Your code isn't portable until it survives a clean build.&lt;/em&gt; If your app only works because the database was assembled "just right" and nobody's had to start fresh — that's not stability, that's a patient time bomb.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vault and the Clerk
&lt;/h2&gt;

&lt;p&gt;Once the database rejected the defaults, I had to put them somewhere else. Which raised the interesting question: where do defaults &lt;em&gt;belong&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Think of your backend as a bank:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The vault (the database):&lt;/strong&gt; stores things safely. Doesn't think. Doesn't decide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The clerk (the application):&lt;/strong&gt; handles logic. Decides what to fill in when nobody specified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The original code was asking the vault to think. The vault refused.&lt;/p&gt;

&lt;p&gt;I moved the defaults to the Model layer — the part of the code that defines what a "Term Summary" actually &lt;em&gt;is&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'overall_comment'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'This student is good.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'principal_comment'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'This student is hardworking.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application handles the logic. The database just stores. Swap Postgres for MySQL tomorrow — the defaults don't care. Rewrite the frontend entirely — the backend doesn't notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle:&lt;/strong&gt; &lt;em&gt;Separation of Concerns.&lt;/em&gt; Each layer minds its own business. The systems that survive change are the ones where nobody's doing someone else's job.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tests Are Receipts, Not Optional
&lt;/h2&gt;

&lt;p&gt;I wrote eight tests. One Factory to generate realistic fake data. Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5 security tests:&lt;/strong&gt; a school admin tries every angle to access an agent's dashboard. Every single one returns &lt;code&gt;401 Unauthorized&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 auth tests:&lt;/strong&gt; agents can still register, log in, get rejected with the wrong password. The front door still works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;"I fixed it"&lt;/em&gt; is a belief. Eight green checkmarks are evidence.&lt;/p&gt;

&lt;p&gt;Six months from now when someone asks &lt;em&gt;"are we certain a school admin can't get into agent data?"&lt;/em&gt; — the answer isn't a Slack message. It's a test suite that runs every time someone touches that code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests are documentation that doesn't go stale and can't be misremembered.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part Worth Keeping
&lt;/h2&gt;

&lt;p&gt;Two things kept surfacing and they're worth separating cleanly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First Principles&lt;/strong&gt; are the physics — the laws that don't change regardless of framework or language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User input is untrusted. Always.&lt;/li&gt;
&lt;li&gt;A primary key that might not exist can't identify anything.&lt;/li&gt;
&lt;li&gt;A database won't efficiently enforce constraints it wasn't built for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Design Patterns&lt;/strong&gt; are the blueprints that implement those laws:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero trust → authentication guards, no URL parameter fallbacks&lt;/li&gt;
&lt;li&gt;Defaults the database won't hold → move them to the Model&lt;/li&gt;
&lt;li&gt;Prove it works → Factories and tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is the tool. The principle is why you pick it up.&lt;/p&gt;




&lt;p&gt;Three questions worth asking about every system you touch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Who owns this responsibility?&lt;/strong&gt; If the answer isn't obvious, that's the bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is actually, fundamentally true here?&lt;/strong&gt; Strip the assumptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you prove it?&lt;/strong&gt; If not, you don't know — you just haven't been wrong yet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The recipes change. The physics don't.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Drop a comment — genuinely curious:&lt;/strong&gt; what's the worst "it worked in production" moment you've walked into? The more specific the horror, the better. 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Backend engineer. I write about systems, architecture, and what happens when you poke at things that were better left unexamined. Portfolio: &lt;a href="https://cycy.is-a.dev" rel="noopener noreferrer"&gt;cycy.is-a.dev&lt;/a&gt; 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>security</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The is-a.dev + Vercel Field Guide: Every Pitfall, Mapped</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Fri, 06 Mar 2026 10:40:03 +0000</pubDate>
      <link>https://dev.to/cynthizo/the-is-adev-vercel-field-guide-every-pitfall-mapped-1g0o</link>
      <guid>https://dev.to/cynthizo/the-is-adev-vercel-field-guide-every-pitfall-mapped-1g0o</guid>
      <description>&lt;p&gt;Free &lt;code&gt;.is-a.dev&lt;/code&gt; subdomains are genuinely one of the better things the dev community has built. Clean URL, no cost, no registrar drama. The whole process is submitting a JSON file via GitHub Pull Request.&lt;/p&gt;

&lt;p&gt;Here's the part nobody front-loads: the repo runs &lt;strong&gt;automated CI tests&lt;/strong&gt; on every PR before a human even looks at it. Fail those, and you're already closed before a reviewer blinks. Pass those, and &lt;em&gt;then&lt;/em&gt; a real human volunteer visits your site and reviews your files.&lt;/p&gt;

&lt;p&gt;Two gates. Different failure modes. This guide covers both.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 0: Check Availability Before Anything Else
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://is-a.dev" rel="noopener noreferrer"&gt;https://is-a.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Search your name. Takes 10 seconds. Do it before you get attached.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub account&lt;/li&gt;
&lt;li&gt;A project &lt;strong&gt;live, deployed, and has actual content&lt;/strong&gt; on Vercel — not a placeholder page&lt;/li&gt;
&lt;li&gt;Vercel dashboard open at &lt;strong&gt;your project → Settings → Domains&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A screenshot of your live site (the PR template requires this)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Fork, Clone, Branch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-github-username/register.git
&lt;span class="nb"&gt;cd &lt;/span&gt;register
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; add-yourname-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fresh branch every time. Don't work on &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Your Domain File
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;domains/yourname.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-github-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CNAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cname.vercel-dns.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the CI tests will catch here
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;record&lt;/code&gt; vs &lt;code&gt;records&lt;/code&gt;&lt;/strong&gt; — The CI schema validation flags &lt;code&gt;record&lt;/code&gt; (singular) immediately. Your PR won't survive automated checks. It's &lt;code&gt;records&lt;/code&gt;, plural, always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invalid JSON&lt;/strong&gt; — A missing comma or unclosed brace and the CI fails. Validate at &lt;a href="https://jsonlint.com" rel="noopener noreferrer"&gt;jsonlint.com&lt;/a&gt; before you push.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File in the wrong place&lt;/strong&gt; — Must be inside &lt;code&gt;domains/&lt;/code&gt;. Not the root, not a subfolder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A records must be arrays&lt;/strong&gt; — If you use an A record instead of CNAME, it has to be: &lt;code&gt;"A": ["76.76.21.21"]&lt;/code&gt; — array syntax, even with one value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CNAME + A in the same file&lt;/strong&gt; — They conflict. CI will catch it. Pick one; for Vercel, CNAME is the right call since their IPs can change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-referencing&lt;/strong&gt; — A CNAME that points back to &lt;code&gt;yourname.is-a.dev&lt;/code&gt; will also get caught.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use CNAME over A record for Vercel. &lt;code&gt;cname.vercel-dns.com&lt;/code&gt; is stable long-term.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3: The Vercel Verification Pitfall 🚨
&lt;/h2&gt;

&lt;p&gt;This is what gets people past CI but stopped by the human reviewer.&lt;/p&gt;

&lt;h3&gt;
  
  
  First — get your TXT token from Vercel
&lt;/h3&gt;

&lt;p&gt;Your project needs to already be live and deployed. Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Vercel project → Settings → Domains → Add Domain&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter &lt;code&gt;yourname.is-a.dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Vercel asks if you want to redirect to &lt;code&gt;www.yourname.is-a.dev&lt;/code&gt; — &lt;strong&gt;do not enable this&lt;/strong&gt; (more on why in a second)&lt;/li&gt;
&lt;li&gt;Select the root domain only → click &lt;strong&gt;"Continue manually"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Copy the TXT string Vercel shows you:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vc-domain-verify=yourname.is-a.dev,sometoken123abc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vercel shows "Invalid Configuration" immediately. That's expected — your PR isn't merged yet. Leave it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where NOT to put the TXT record
&lt;/h3&gt;

&lt;p&gt;The obvious move: add the TXT record into &lt;code&gt;yourname.json&lt;/code&gt; next to the CNAME. Tidy, logical, wrong.&lt;/p&gt;

&lt;p&gt;is-a.dev requires verification records in their &lt;strong&gt;own dedicated file&lt;/strong&gt;. For Vercel, that file is &lt;code&gt;_vercel.yourname.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-github-username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"TXT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vc-domain-verify=yourname.is-a.dev,your-token-from-vercel"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You end up with &lt;strong&gt;two files&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Does what&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yourname.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Routes traffic to Vercel via CNAME&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_vercel.yourname.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Proves ownership to Vercel via TXT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One routes. One verifies. That's the unlock.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3b: The WWW Redirect Trap 🪤
&lt;/h2&gt;

&lt;p&gt;Back in Step 3 when Vercel asks about redirecting to &lt;code&gt;www.yourname.is-a.dev&lt;/code&gt; — here's why you skip it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;www.yourname.is-a.dev&lt;/code&gt; is a completely separate subdomain. It needs its &lt;strong&gt;own JSON file and its own PR&lt;/strong&gt; in the is-a.dev repo. If you enable the redirect without registering &lt;code&gt;www&lt;/code&gt;, Vercel goes looking for a domain it can't find — and your site serves a 404 instead of your portfolio.&lt;/p&gt;

&lt;p&gt;Worse: if you then try to remove the redirect and go back to the root domain, Vercel can get stuck demanding a new TXT verification token for the &lt;code&gt;www&lt;/code&gt; subdomain before it lets you proceed. It's an annoying loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you accidentally clicked it already:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Vercel → Project Settings → Domains&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Find &lt;code&gt;www.yourname.is-a.dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete it 🗑️&lt;/li&gt;
&lt;li&gt;Root domain only stays&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Propagates in about 60 seconds. Back to normal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Submit the PR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add domains/yourname.json domains/_vercel.yourname.json
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat(domain): add yourname.is-a.dev with Vercel CNAME and verification"&lt;/span&gt;
git push origin add-yourname-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a PR against &lt;code&gt;is-a-dev/register&lt;/code&gt;. An automated welcome message appears when you do — read it. It tells you exactly what reviewers check. Short version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fill in the PR template&lt;/strong&gt; — don't delete it or swap it out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include a screenshot of your live site&lt;/strong&gt; — not optional, reviewers look for it&lt;/li&gt;
&lt;li&gt;Mention the domain is already added in Vercel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reviewers are volunteers. A complete, clean submission is the only reliable fast path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Pitfall Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What kills your PR&lt;/th&gt;
&lt;th&gt;When it's caught&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;record&lt;/code&gt; not &lt;code&gt;records&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CI — automated&lt;/td&gt;
&lt;td&gt;Fix the key, validate JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid JSON syntax&lt;/td&gt;
&lt;td&gt;CI — automated&lt;/td&gt;
&lt;td&gt;Run through jsonlint.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File outside &lt;code&gt;domains/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CI — automated&lt;/td&gt;
&lt;td&gt;Move it to the right folder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A record as a string not array&lt;/td&gt;
&lt;td&gt;CI — automated&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"A": ["ip"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNAME + A in same file&lt;/td&gt;
&lt;td&gt;CI — automated&lt;/td&gt;
&lt;td&gt;Pick one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNAME pointing to itself&lt;/td&gt;
&lt;td&gt;CI — automated&lt;/td&gt;
&lt;td&gt;Don't self-reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TXT token inside main domain file&lt;/td&gt;
&lt;td&gt;Human reviewer&lt;/td&gt;
&lt;td&gt;Move to &lt;code&gt;_vercel.yourname.json&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain added to Vercel but shows 404&lt;/td&gt;
&lt;td&gt;Human reviewer&lt;/td&gt;
&lt;td&gt;Add domain first, let Vercel sit in "pending"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No screenshot in PR description&lt;/td&gt;
&lt;td&gt;Human reviewer&lt;/td&gt;
&lt;td&gt;Add one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Site is under construction&lt;/td&gt;
&lt;td&gt;Human reviewer&lt;/td&gt;
&lt;td&gt;Finish it, then submit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;www&lt;/code&gt; redirect enabled without registering &lt;code&gt;www&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Human reviewer / post-merge&lt;/td&gt;
&lt;td&gt;Delete &lt;code&gt;www&lt;/code&gt; entry in Vercel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR goes stale after maintainer asks for changes&lt;/td&gt;
&lt;td&gt;Administrative&lt;/td&gt;
&lt;td&gt;Respond within a few days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name already taken by a pending PR&lt;/td&gt;
&lt;td&gt;Administrative&lt;/td&gt;
&lt;td&gt;Check open PRs before submitting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nested subdomain, parent doesn't exist&lt;/td&gt;
&lt;td&gt;CI or reviewer&lt;/td&gt;
&lt;td&gt;Register parent domain first&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  GitHub Pages? Same Pattern
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-github-username"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CNAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-github-username.github.io"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification file: &lt;code&gt;_github-pages-challenge-yourname.json&lt;/code&gt; — same structure, different prefix. The &lt;code&gt;_platform.yourname.json&lt;/code&gt; naming convention applies across the board.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pre-Submit Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Name available at &lt;a href="https://is-a.dev" rel="noopener noreferrer"&gt;is-a.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Fresh branch off main&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;domains/yourname.json&lt;/code&gt; — CNAME, &lt;code&gt;records&lt;/code&gt; plural, valid JSON&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;domains/_vercel.yourname.json&lt;/code&gt; — TXT token from Vercel&lt;/li&gt;
&lt;li&gt;[ ] Domain added to Vercel Settings → Domains, root only, no www redirect&lt;/li&gt;
&lt;li&gt;[ ] Site is live with real content&lt;/li&gt;
&lt;li&gt;[ ] Screenshot of live site ready for PR description&lt;/li&gt;
&lt;li&gt;[ ] PR template filled out, not replaced&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  After the Merge
&lt;/h2&gt;

&lt;p&gt;Usually live within minutes. Still seeing the is-a.dev homepage after 15–20 min? Flush DNS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dscacheutil &lt;span class="nt"&gt;-flushcache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;killall &lt;span class="nt"&gt;-HUP&lt;/span&gt; mDNSResponder

&lt;span class="c"&gt;# Linux&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemd-resolve &lt;span class="nt"&gt;--flush-caches&lt;/span&gt;

&lt;span class="c"&gt;# Windows&lt;/span&gt;
ipconfig /flushdns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enable &lt;strong&gt;Enforce HTTPS&lt;/strong&gt; in Vercel. You have a clean URL now — secure it.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check &lt;a href="https://is-a.dev" rel="noopener noreferrer"&gt;is-a.dev&lt;/a&gt; first&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;records&lt;/code&gt; — plural, or CI rejects you immediately&lt;/li&gt;
&lt;li&gt;CNAME to &lt;code&gt;cname.vercel-dns.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Get TXT token: &lt;strong&gt;Vercel → project → Settings → Domains → Add Domain → Continue manually&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;TXT token goes in &lt;code&gt;_vercel.yourname.json&lt;/code&gt; — separate file, not the main one&lt;/li&gt;
&lt;li&gt;Root domain only in Vercel — skip the www redirect&lt;/li&gt;
&lt;li&gt;Screenshot in the PR description&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two gates. Two files. Right keys. That's the whole thing. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by a developer who now has a working &lt;code&gt;.is-a.dev&lt;/code&gt; domain and a portfolio full of backend projects to show for it — &lt;a href="https://cycy.is-a.dev" rel="noopener noreferrer"&gt;cycy.is-a.dev&lt;/a&gt; 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vercel</category>
      <category>dns</category>
      <category>git</category>
    </item>
    <item>
      <title>this was a really fun drive 🚘 ...plus I totally love synthwave 😍

starting cursing here: https://sundaydevdrive.pilotronica.com
contribute to the project here: https://github.com/georgekobaidze/sunday-dev-drive

well done @Giorgi Kobaidze</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Fri, 06 Mar 2026 06:55:25 +0000</pubDate>
      <link>https://dev.to/cynthizo/this-was-a-really-fun-drive-plus-i-totally-love-synthwave-starting-cursing-here-2abb</link>
      <guid>https://dev.to/cynthizo/this-was-a-really-fun-drive-plus-i-totally-love-synthwave-starting-cursing-here-2abb</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/georgekobaidze/sunday-dev-drive-a-synthwave-driving-experience-through-your-dev-community-articles-5032" class="crayons-story__hidden-navigation-link"&gt;Sunday DEV Drive: A Synthwave Driving Experience Through Your DEV Community Articles&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/georgekobaidze/sunday-dev-drive-a-synthwave-driving-experience-through-your-dev-community-articles-5032" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;DEV Weekend Challenge: Community&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/georgekobaidze" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F55651%2F29e2a161-9d78-410b-a6e5-9aca17092fa3.jpeg" alt="georgekobaidze profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/georgekobaidze" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Giorgi Kobaidze
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Giorgi Kobaidze
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3299096" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/georgekobaidze" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F55651%2F29e2a161-9d78-410b-a6e5-9aca17092fa3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Giorgi Kobaidze&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/georgekobaidze/sunday-dev-drive-a-synthwave-driving-experience-through-your-dev-community-articles-5032" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 1&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/georgekobaidze/sunday-dev-drive-a-synthwave-driving-experience-through-your-dev-community-articles-5032" id="article-link-3299096"&gt;
          Sunday DEV Drive: A Synthwave Driving Experience Through Your DEV Community Articles
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/weekendchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;weekendchallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/georgekobaidze/sunday-dev-drive-a-synthwave-driving-experience-through-your-dev-community-articles-5032" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;64&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/georgekobaidze/sunday-dev-drive-a-synthwave-driving-experience-through-your-dev-community-articles-5032#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              23&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://sundaydevdrive.pilotronica.com/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundaydevdrive.pilotronica.com%2Fassets%2Fposter.jpg" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://sundaydevdrive.pilotronica.com/" rel="noopener noreferrer" class="c-link"&gt;
            Sunday DEV Drive
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Drive through your DEV Community articles in a synthwave world. Your posts become neon billboards along an endless road.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundaydevdrive.pilotronica.com%2Fassets%2Ffavicon.ico"&gt;
          sundaydevdrive.pilotronica.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;br&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/georgekobaidze/sunday-dev-drive" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopengraph.githubassets.com%2Fdff636610657bbe9598c1397fb020f2a9b9136d3b0b1eb92e423ae5919423e63%2Fgeorgekobaidze%2Fsunday-dev-drive" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/georgekobaidze/sunday-dev-drive" rel="noopener noreferrer" class="c-link"&gt;
            GitHub - georgekobaidze/sunday-dev-drive: A synthwave driving experience through your DEV Community articles · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A synthwave driving experience through your DEV Community articles - georgekobaidze/sunday-dev-drive
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Deep Dive: Resolving DNS Issues with GitHub and Understanding SSH vs HTTPS</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Thu, 05 Mar 2026 10:39:57 +0000</pubDate>
      <link>https://dev.to/cynthizo/deep-dive-resolving-dns-issues-with-github-and-understanding-ssh-vs-https-4bom</link>
      <guid>https://dev.to/cynthizo/deep-dive-resolving-dns-issues-with-github-and-understanding-ssh-vs-https-4bom</guid>
      <description>&lt;p&gt;&lt;strong&gt;Ever tried pushing your code to GitHub, only to be stopped by a timeout error that makes zero sense?&lt;/strong&gt; That's exactly what happened to me — and what started as a frustrating blocker turned into a genuinely useful deep dive into DNS, network routing, and Git's connection methods. 🚀&lt;/p&gt;

&lt;p&gt;Let me walk you through every step.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: GitHub, You're Killing Me 😩
&lt;/h2&gt;

&lt;p&gt;Everything was going fine until I ran a routine push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin feat/add-tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of the usual success, I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;fatal: unable to access 'https://github.com/your-username/your-repo.git/':
Failed to connect to github.com port 443 after 75079 ms: Couldn't connect to server
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;75 seconds of waiting, then nothing. Time to dig in. 🔍&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Is GitHub Actually Down? 🌐
&lt;/h2&gt;

&lt;p&gt;Before blaming my own setup, I checked whether GitHub was reachable at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; https://github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response? &lt;strong&gt;&lt;code&gt;000&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;000&lt;/code&gt; status code from &lt;code&gt;curl&lt;/code&gt; means the request never even got a response — no HTTP handshake, no connection, nothing. GitHub's HTTPS port was completely unreachable from my machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Ping &amp;amp; DNS Lookup — Finding the Culprit 🕵️‍♂️
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ping test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 github.com
PING github.com &lt;span class="o"&gt;(&lt;/span&gt;20.87.245.0&lt;span class="o"&gt;)&lt;/span&gt;: 56 data bytes
Request &lt;span class="nb"&gt;timeout &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;icmp_seq 0
64 bytes from 20.87.245.0: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;114 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;72.940 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Packet loss right away. The IP &lt;code&gt;20.87.245.0&lt;/code&gt; was responding inconsistently — not the behavior you'd expect from GitHub's infrastructure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Packet loss in a ping doesn't always mean slow internet. It often means the specific IP you're being routed to has a problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  DNS lookup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nslookup github.com
Name:    github.com
Address: 20.87.245.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There it was. DNS was resolving &lt;code&gt;github.com&lt;/code&gt; to &lt;code&gt;20.87.245.0&lt;/code&gt; — a &lt;strong&gt;broken or misrouted IP&lt;/strong&gt;. Large services like GitHub use many IPs across their infrastructure. When DNS caching or propagation goes wrong, you can end up pointed at one that's temporarily dead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Port Check — Confirm It's Not Just a General Outage 🔌
&lt;/h2&gt;

&lt;p&gt;To rule out a broader network issue, I checked whether GitHub's HTTPS port (443) specifically was blocked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nc &lt;span class="nt"&gt;-zv&lt;/span&gt; github.com 443 &lt;span class="nt"&gt;-w&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No connection. Port 443 was unresponsive via that IP.&lt;/p&gt;

&lt;p&gt;But this raised an interesting question — &lt;strong&gt;if HTTPS (port 443) is blocked, what about SSH (port 22)?&lt;/strong&gt; I kept that in mind for later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Traceroute — Where Is the Connection Actually Dying? 🗺️
&lt;/h2&gt;

&lt;p&gt;Running a traceroute showed exactly where packets were getting dropped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;traceroute github.com
 1  192.168.x.x &lt;span class="o"&gt;(&lt;/span&gt;192.168.x.x&lt;span class="o"&gt;)&lt;/span&gt;    14.672 ms
 2  10.x.x.x &lt;span class="o"&gt;(&lt;/span&gt;10.x.x.x&lt;span class="o"&gt;)&lt;/span&gt;          9.098 ms
 3  &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;
 4  &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;
 5  &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;(Local IPs anonymised)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hops 1 and 2 — my local router and ISP gateway — responded fine. After that: silence. The connection was dying &lt;strong&gt;somewhere in the ISP's routing infrastructure&lt;/strong&gt;, well before reaching GitHub's servers.&lt;/p&gt;

&lt;p&gt;This is a classic sign of either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;routing failure&lt;/strong&gt; at the ISP level&lt;/li&gt;
&lt;li&gt;Traffic being directed to a &lt;strong&gt;bad IP&lt;/strong&gt; that simply drops packets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The DNS evidence from earlier made the latter the most likely culprit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Verify the Fix With a Known-Good IP 🧪
&lt;/h2&gt;

&lt;p&gt;Before touching any system config, I tested whether the &lt;em&gt;network itself&lt;/em&gt; was fine by bypassing DNS entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--resolve&lt;/span&gt; github.com:443:140.82.121.3 https://github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This forces &lt;code&gt;curl&lt;/code&gt; to connect to &lt;code&gt;140.82.121.3&lt;/code&gt; (a legitimate GitHub IP) while keeping the hostname intact for TLS. The result? &lt;strong&gt;200 OK.&lt;/strong&gt; ✅&lt;/p&gt;

&lt;p&gt;The network was fine. The problem was purely DNS returning a bad IP.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How to find legitimate GitHub IPs:&lt;/strong&gt; Check &lt;a href="https://api.github.com/meta" rel="noopener noreferrer"&gt;https://api.github.com/meta&lt;/a&gt; — GitHub publishes their official IP ranges here.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 6: Override DNS via &lt;code&gt;/etc/hosts&lt;/code&gt; 🔧
&lt;/h2&gt;

&lt;p&gt;With the root cause confirmed, the fastest fix was to bypass DNS locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"140.82.121.3 github.com"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or edit the file manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/hosts
&lt;span class="c"&gt;# Add this line:&lt;/span&gt;
140.82.121.3 github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells your OS: &lt;em&gt;"Don't ask DNS where github.com is — I'm telling you directly."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After saving, GitHub loaded instantly in the browser. But the &lt;code&gt;git push&lt;/code&gt; was still failing — because my remote was still using HTTPS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Switch Git Remote from HTTPS to SSH 🔑
&lt;/h2&gt;

&lt;p&gt;Here's what I had been missing. Even with the &lt;code&gt;/etc/hosts&lt;/code&gt; fix, port 443 was still being affected by the underlying routing issue. SSH uses &lt;strong&gt;port 22&lt;/strong&gt;, which was completely unaffected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check your current remote:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# origin  https://github.com/your-username/your-repo.git (fetch)&lt;/span&gt;
&lt;span class="c"&gt;# origin  https://github.com/your-username/your-repo.git (push)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Switch to SSH:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote set-url origin git@github.com:your-username/your-repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# origin  git@github.com:your-username/your-repo.git (fetch)&lt;/span&gt;
&lt;span class="c"&gt;# origin  git@github.com:your-username/your-repo.git (push)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin feat/add-tests
&lt;span class="c"&gt;# ✅ Success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a quick comparison of both methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;td&gt;Username + PAT token&lt;/td&gt;
&lt;td&gt;Simple setups, one-off clones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSH&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;SSH key pair&lt;/td&gt;
&lt;td&gt;Frequent pushes, automation, CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SSH is worth setting up properly if you haven't — no tokens to rotate, no passwords to enter, and as this situation showed, port 22 is far less likely to be caught up in DNS/routing failures than 443.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways 💡
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A &lt;code&gt;curl&lt;/code&gt; status of &lt;code&gt;000&lt;/code&gt; means zero connectivity&lt;/strong&gt; — not a server error, not a redirect. Nothing got through.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DNS can return broken IPs.&lt;/strong&gt; Large services like GitHub use many IPs. Caching issues can leave you pointed at one that's dead or misrouted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Traceroute tells you &lt;em&gt;where&lt;/em&gt; things break.&lt;/strong&gt; If packets die at hop 3+, the issue is upstream of you — your ISP or beyond.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;/etc/hosts&lt;/code&gt; is a surgical bypass tool.&lt;/strong&gt; It skips DNS entirely for a specific hostname. Useful in emergencies, but remember to revert it once DNS is healthy again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SSH over HTTPS for Git — always, if you can.&lt;/strong&gt; It's more secure, needs no token management, and uses a different port that's often unaffected when HTTPS routes break.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always verify IPs against official sources.&lt;/strong&gt; Hardcoding a rogue IP in &lt;code&gt;/etc/hosts&lt;/code&gt; is a real MITM vector. Use &lt;a href="https://api.github.com/meta" rel="noopener noreferrer"&gt;api.github.com/meta&lt;/a&gt; to confirm.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Full Troubleshooting Cheatsheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Check if HTTPS is reachable at all&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; https://github.com

&lt;span class="c"&gt;# 2. Check DNS resolution&lt;/span&gt;
nslookup github.com

&lt;span class="c"&gt;# 3. Test connectivity to a known-good IP without changing DNS&lt;/span&gt;
curl &lt;span class="nt"&gt;--resolve&lt;/span&gt; github.com:443:140.82.121.3 https://github.com

&lt;span class="c"&gt;# 4. Check if port 443 is open&lt;/span&gt;
nc &lt;span class="nt"&gt;-zv&lt;/span&gt; github.com 443 &lt;span class="nt"&gt;-w&lt;/span&gt; 5

&lt;span class="c"&gt;# 5. Trace where packets are dying&lt;/span&gt;
traceroute github.com

&lt;span class="c"&gt;# 6. Override DNS locally&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"140.82.121.3 github.com"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts

&lt;span class="c"&gt;# 7. Check your Git remote&lt;/span&gt;
git remote &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# 8. Switch remote to SSH&lt;/span&gt;
git remote set-url origin git@github.com:your-username/your-repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;What looked like a random GitHub outage turned out to be a DNS routing failure — solvable with a two-line fix once you know what you're looking for. The traceroute and &lt;code&gt;curl&lt;/code&gt; tests were the real turning point: they made the invisible visible, showing exactly where the connection was breaking and why.&lt;/p&gt;

&lt;p&gt;If you take nothing else from this: &lt;strong&gt;learn to read traceroutes, and set up SSH for Git.&lt;/strong&gt; Both have saved me hours of confusion.&lt;/p&gt;

&lt;p&gt;Got questions, or your own DNS war stories? Drop them in the comments 👇&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git push&lt;/code&gt; failed with a port 443 timeout&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl&lt;/code&gt; returned &lt;code&gt;000&lt;/code&gt; — no connection at all&lt;/li&gt;
&lt;li&gt;DNS was resolving GitHub to a broken IP (&lt;code&gt;20.87.245.0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Traceroute confirmed the failure was upstream, not local&lt;/li&gt;
&lt;li&gt;Fixed short-term with &lt;code&gt;/etc/hosts&lt;/code&gt; pointing to a working IP&lt;/li&gt;
&lt;li&gt;Fixed properly by switching Git remote from HTTPS → SSH (port 22)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>networking</category>
      <category>devops</category>
    </item>
    <item>
      <title>I resonate so much with this</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Sun, 01 Mar 2026 18:34:40 +0000</pubDate>
      <link>https://dev.to/cynthizo/-14f5</link>
      <guid>https://dev.to/cynthizo/-14f5</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/the_nortern_dev" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3630167%2F2e206d7e-04d3-484b-8a73-1f98d17a0e1a.png" alt="the_nortern_dev"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/the_nortern_dev/the-hardest-part-of-being-a-developer-isnt-coding-its-disappearing-quietly-52l" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;The Hardest Part of Being a Developer Isn’t Coding. It’s Disappearing Quietly.&lt;/h2&gt;
      &lt;h3&gt;NorthernDev ・ Feb 28&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#discuss&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#mentalhealth&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>discuss</category>
      <category>mentalhealth</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>I Almost Filed a Bug Report. Then I Pressed Ctrl+P</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Sun, 01 Mar 2026 10:45:16 +0000</pubDate>
      <link>https://dev.to/cynthizo/i-almost-filed-a-bug-report-then-i-pressed-ctrlp-2jck</link>
      <guid>https://dev.to/cynthizo/i-almost-filed-a-bug-report-then-i-pressed-ctrlp-2jck</guid>
      <description>&lt;p&gt;I was using &lt;strong&gt;Kilo CLI v7.0.33&lt;/strong&gt; and something felt off. I could scroll fine — but there was no scrollbar indicator. No sense of where I was in a long session. Other CLIs had it. Mine didn't.&lt;/p&gt;

&lt;p&gt;My immediate take: &lt;em&gt;the scrollbar is broken.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I went digging.&lt;/p&gt;




&lt;h2&gt;
  
  
  The GitHub Rabbit Hole
&lt;/h2&gt;

&lt;p&gt;I went through OpenCode issues, Kilo issues, anything mentioning scroll + Warp + iTerm. Left a couple of comments piecing together what I thought was happening:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/anomalyco/opencode/issues/2500" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        scrollbars missing
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#2500&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/ubuntupunk" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F246662%3Fv%3D4" alt="ubuntupunk avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/ubuntupunk" rel="noopener noreferrer"&gt;ubuntupunk&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/anomalyco/opencode/issues/2500" rel="noopener noreferrer"&gt;&lt;time&gt;Sep 08, 2025&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;I can't seem to scroll up to read what opencode is outputting. I don't have this problem with gemini-cli and any other cli  so its not terminal related.
The opencode scrollbar is just a sold bar for some reason&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/user-attachments/assets/f340d163-0352-4d07-935a-9aeddcccceb4"&gt;&lt;img width="1560" height="896" alt="Image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Ff340d163-0352-4d07-935a-9aeddcccceb4"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anomalyco/opencode/issues/2500" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/anomalyco/opencode/issues/6209" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Cannot scroll on opencode when using iterm
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#6209&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/anugrahsinghal" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F18058884%3Fv%3D4" alt="anugrahsinghal avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/anugrahsinghal" rel="noopener noreferrer"&gt;anugrahsinghal&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/anomalyco/opencode/issues/6209" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 26, 2025&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Description&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;When trying to scroll the opencode TUI on iterm, it scrolls the input box but not the output of the previous command&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;OpenCode version&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;1.0.203&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Steps to reproduce&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Install iTerm2&lt;/li&gt;
&lt;li&gt;opencode&lt;/li&gt;
&lt;li&gt;!ls # to see basic output - should be long enough to overflow from view&lt;/li&gt;
&lt;li&gt;scroll&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screenshot and/or share link&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/user-attachments/assets/eea1ae7e-6401-4ef8-b30d-d5265fee28ca" rel="noopener noreferrer"&gt;https://github.com/user-attachments/assets/eea1ae7e-6401-4ef8-b30d-d5265fee28ca&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Operating System&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;26.2&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Terminal&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;iTerm2&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anomalyco/opencode/issues/6209" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;The threads were noisy but a pattern emerged — people were actually describing &lt;strong&gt;two different problems&lt;/strong&gt; and conflating them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1 — Mouse scroll not working at all&lt;/strong&gt; (terminal layer)&lt;br&gt;
Warp and iTerm need &lt;strong&gt;Mouse Reporting&lt;/strong&gt; enabled before they'll forward scroll events to a full-screen TUI. Without it, your scroll wheel just doesn't reach the app. Fix is in terminal settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2 — Scrollbar not visible&lt;/strong&gt; (app layer)&lt;br&gt;
Completely separate. Scroll events arriving fine, but the indicator is just... off.&lt;/p&gt;

&lt;p&gt;I had Problem 2. But the GitHub threads mostly discussed Problem 1.&lt;/p&gt;



&lt;p&gt;Here’s what it looked like in my terminal — no visible scrollbar, long output, no sense of position and it was consistent in all the themes!&lt;/p&gt;


  


&lt;p&gt;At this point, I was convinced it was broken.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wait, What Am I Even Running?
&lt;/h2&gt;

&lt;p&gt;Before assuming anything, I needed to understand the actual architecture. Kilo isn't just OpenCode — it's a fork:&lt;/p&gt;



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



&lt;p&gt;So I went to the source.&lt;/p&gt;

&lt;p&gt;Buried in the session route, I found it. The scrollbar wasn’t missing at all — it was &lt;strong&gt;disabled by default&lt;/strong&gt;. A KV-backed feature flag, persistent across sessions, but initialized to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I was one tab away from opening an enhancement issue.&lt;/p&gt;

&lt;p&gt;Inside:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;opencode/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;showScrollbar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShowScrollbar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scrollbar_visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;verticalScrollbarOptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
  &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;showScrollbar&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the moment everything clicked.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The scrollbar existed.&lt;/li&gt;
&lt;li&gt;It was toggleable.&lt;/li&gt;
&lt;li&gt;And by default — it was off.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Then I Pressed Ctrl+P
&lt;/h2&gt;

&lt;p&gt;Command Palette. I typed "scroll."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Toggle session scrollbar&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I pressed it. The scrollbar appeared.&lt;/p&gt;

&lt;p&gt;It had been there the whole time.&lt;/p&gt;




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

&lt;p&gt;Not a bug. Not a missing feature. A &lt;strong&gt;discoverability gap&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The scrollbar is implemented, toggleable, persistent — just hidden behind a keybind nobody thinks to try when they're confused about scroll behavior. That's a UX conversation worth having with maintainers, not a bug report.&lt;/p&gt;

&lt;p&gt;After piecing everything together, I went back to the GitHub threads.&lt;/p&gt;

&lt;p&gt;Instead of filing a new issue, I left a couple of comments clarifying what I’d found — separating the terminal mouse-reporting problem from the scrollbar visibility toggle. A small thing, but hopefully useful signal in a noisy thread.&lt;/p&gt;

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

&lt;p&gt;--&lt;/p&gt;

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

&lt;p&gt;Sometimes contributing isn’t about opening something new.&lt;br&gt;
It’s about tightening what already exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Separate layers before blaming.&lt;/strong&gt; Terminal config and app state are different things. Conflating them sends you in circles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read source before filing.&lt;/strong&gt; That &lt;code&gt;false&lt;/code&gt; default was right there. Five minutes of reading saved a maintainer from triaging noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forks are their own thing.&lt;/strong&gt; Upstream behavior isn't a reliable guide. Check what the fork actually exposes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discoverability is a real problem.&lt;/strong&gt; If multiple users independently can't find a feature, that's signal — even if the feature works perfectly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Fix if You're Here for the Answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Scroll not working at all → enable Mouse Reporting in your terminal settings&lt;/li&gt;
&lt;li&gt;Scroll works but no scrollbar → &lt;code&gt;Ctrl+P&lt;/code&gt; → search "scrollbar" → toggle on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It'll persist across sessions once set.&lt;/p&gt;




</description>
      <category>kilocli</category>
      <category>opencode</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Flutter Development Environment Setup (Apple Silicon macOS)</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Tue, 10 Feb 2026 18:55:35 +0000</pubDate>
      <link>https://dev.to/cynthizo/flutter-development-environment-setup-apple-silicon-macos-3m2h</link>
      <guid>https://dev.to/cynthizo/flutter-development-environment-setup-apple-silicon-macos-3m2h</guid>
      <description>&lt;p&gt;This document is a &lt;strong&gt;complete, reproducible guide&lt;/strong&gt; based on a real troubleshooting journey. It explains &lt;strong&gt;what we were trying to achieve&lt;/strong&gt;, &lt;strong&gt;why each tool exists&lt;/strong&gt;, &lt;strong&gt;how to install everything cleanly&lt;/strong&gt;, and &lt;strong&gt;how to debug the exact problems we encountered&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is designed so that you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uninstall everything&lt;/li&gt;
&lt;li&gt;Reinstall from scratch&lt;/li&gt;
&lt;li&gt;Understand &lt;em&gt;why&lt;/em&gt; each step exists&lt;/li&gt;
&lt;li&gt;Recover quickly if something breaks again&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Goal: What We Were Trying to Achieve
&lt;/h2&gt;

&lt;p&gt;We wanted a &lt;strong&gt;stable Flutter development environment&lt;/strong&gt; on &lt;strong&gt;Apple Silicon macOS&lt;/strong&gt; that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build and run an existing &lt;strong&gt;Flutter Android app&lt;/strong&gt; (using a &lt;em&gt;physical Android device&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Compile and run the same Flutter codebase for &lt;strong&gt;iOS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Build and run the app as a &lt;strong&gt;native macOS desktop app&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Avoid Android emulators (low-RAM optimization)&lt;/li&gt;
&lt;li&gt;Be repeatable and understandable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Mental Model: How the Tools Fit Together
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Flutter
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;orchestrator&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Reads your Dart code&lt;/li&gt;
&lt;li&gt;Decides which platform you are targeting&lt;/li&gt;
&lt;li&gt;Delegates work to platform-specific tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dart
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The programming language of your app&lt;/li&gt;
&lt;li&gt;Bundled with Flutter (you don’t install it separately)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Android Studio
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not required&lt;/strong&gt; to build Flutter apps&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Used mainly to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download Android SDK components&lt;/li&gt;
&lt;li&gt;Manage SDK versions&lt;/li&gt;
&lt;li&gt;Debug Android-specific issues&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Android SDK
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Required to build Android apps&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;adb&lt;/code&gt; (talks to your Android phone)&lt;/li&gt;
&lt;li&gt;build-tools&lt;/li&gt;
&lt;li&gt;platform-tools&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Android Command-Line Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Provide &lt;code&gt;sdkmanager&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Required for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepting Android licenses&lt;/li&gt;
&lt;li&gt;Verifying SDK correctness&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Flutter depends on these directly&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Java (JDK)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Android build tools are written in Java&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Required for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sdkmanager&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Gradle builds&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Java &lt;strong&gt;must&lt;/strong&gt; be visible to macOS (JAVA_HOME must be correct)&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Xcode
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Required for &lt;strong&gt;any&lt;/strong&gt; iOS or macOS build&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS simulator&lt;/li&gt;
&lt;li&gt;macOS compiler&lt;/li&gt;
&lt;li&gt;Code signing&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  CocoaPods
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;iOS dependency manager&lt;/li&gt;
&lt;li&gt;Required because Flutter plugins include native iOS code&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Clean Install Order (Recommended)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Install Xcode
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install Xcode from the App Store&lt;/li&gt;
&lt;li&gt;Open Xcode once&lt;/li&gt;
&lt;li&gt;Accept license:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;xcodebuild &lt;span class="nt"&gt;-license&lt;/span&gt; accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install command-line tools:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   xcode-select &lt;span class="nt"&gt;--install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3.2 Install Homebrew
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to PATH (Apple Silicon):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(/opt/homebrew/bin/brew shellenv)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zprofile
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;/opt/homebrew/bin/brew shellenv&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3.3 Install Java (Android requirement)
&lt;/h3&gt;

&lt;p&gt;Install Java 17:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;openjdk@17
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make macOS aware of Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-sfn&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk &lt;span class="se"&gt;\&lt;/span&gt;
  /Library/Java/JavaVirtualMachines/openjdk-17.jdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set JAVA_HOME dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export JAVA_HOME=$(/usr/libexec/java_home)'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$JAVA_HOME/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
/usr/libexec/java_home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3.4 Install Flutter (Official ZIP – NOT Homebrew)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Download Flutter macOS &lt;strong&gt;Apple Silicon&lt;/strong&gt; zip&lt;/li&gt;
&lt;li&gt;Extract via Terminal (avoid Finder quarantine issues):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;cd&lt;/span&gt; ~/Downloads
   unzip flutter_macos_arm64&lt;span class="k"&gt;*&lt;/span&gt;.zip
   &lt;span class="nb"&gt;mv &lt;/span&gt;flutter ~/development/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Remove quarantine:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   xattr &lt;span class="nt"&gt;-dr&lt;/span&gt; com.apple.quarantine ~/development/flutter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add to PATH:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$PATH:$HOME/development/flutter/bin"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
   &lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Verify:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   flutter &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3.5 Install Android Studio
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Download Apple Silicon version&lt;/li&gt;
&lt;li&gt;Drag &lt;strong&gt;Android Studio.app&lt;/strong&gt; into &lt;code&gt;/Applications&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove quarantine:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;xattr &lt;span class="nt"&gt;-dr&lt;/span&gt; com.apple.quarantine &lt;span class="s2"&gt;"/Applications/Android Studio.app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Launch Android Studio&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  3.6 Install Android SDK + Command-Line Tools
&lt;/h3&gt;

&lt;p&gt;In Android Studio:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;SDK Manager&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set SDK location:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   /Users/&amp;lt;your-user&amp;gt;/Library/Android/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Android SDK Platform&lt;/li&gt;
&lt;li&gt;Build-Tools&lt;/li&gt;
&lt;li&gt;Platform-Tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Command-line Tools (latest)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; ~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export ANDROID_HOME="$HOME/Library/Android/sdk"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accept licenses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter doctor &lt;span class="nt"&gt;--android-licenses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3.7 Install CocoaPods (iOS requirement)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;cocoapods
pod &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3.8 Install iOS Simulator Runtime
&lt;/h3&gt;

&lt;p&gt;In Xcode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Xcode → Settings → Components&lt;/li&gt;
&lt;li&gt;Install latest iOS Simulator runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun simctl list runtimes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Building Your Flutter App
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Android (Physical Device)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter run &lt;span class="nt"&gt;-d&lt;/span&gt; android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  iOS (Simulator)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ios
pod &lt;span class="nb"&gt;install
cd&lt;/span&gt; ..
flutter run &lt;span class="nt"&gt;-d&lt;/span&gt; ios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  macOS Desktop
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter config &lt;span class="nt"&gt;--enable-macos-desktop&lt;/span&gt;
flutter pub get
flutter run &lt;span class="nt"&gt;-d&lt;/span&gt; macos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Release build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter build macos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Troubleshooting Reference (Problems We Solved)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Problem: “dart is damaged and can’t be opened”
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; macOS quarantine&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xattr &lt;span class="nt"&gt;-dr&lt;/span&gt; com.apple.quarantine ~/development/flutter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Problem: Android SDK exists but Flutter can’t find it
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Missing command-line tools&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Install &lt;code&gt;cmdline-tools/latest&lt;/code&gt; and verify &lt;code&gt;sdkmanager&lt;/code&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Problem: &lt;code&gt;sdkmanager&lt;/code&gt; exists but fails with JAVA_HOME error
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Homebrew Java not visible to macOS&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Symlink Java into &lt;code&gt;/Library/Java/JavaVirtualMachines&lt;/code&gt; and set JAVA_HOME dynamically&lt;/p&gt;


&lt;h3&gt;
  
  
  Problem: &lt;code&gt;/usr/libexec/java_home&lt;/code&gt; returns nothing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Java installed but not registered with macOS&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Manual symlink (see Java section)&lt;/p&gt;


&lt;h3&gt;
  
  
  Problem: Android Studio installed but no SDK directory
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; SDK Manager never ran&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Install SDK via Android Studio → SDK Manager&lt;/p&gt;


&lt;h3&gt;
  
  
  Problem: iOS simulator missing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; No simulator runtime downloaded&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Xcode → Settings → Components → Install iOS runtime&lt;/p&gt;


&lt;h3&gt;
  
  
  Problem: Chrome warning in &lt;code&gt;flutter doctor&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Meaning:&lt;/strong&gt; Only affects Flutter Web&lt;br&gt;
&lt;strong&gt;Fix (optional):&lt;/strong&gt; Set &lt;code&gt;CHROME_EXECUTABLE&lt;/code&gt; to Brave or install Chrome&lt;/p&gt;


&lt;h2&gt;
  
  
  6. Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Flutter is the &lt;strong&gt;coordinator&lt;/strong&gt;, not the builder&lt;/li&gt;
&lt;li&gt;Android Studio is optional but useful&lt;/li&gt;
&lt;li&gt;Java is critical for Android builds&lt;/li&gt;
&lt;li&gt;macOS security (Gatekeeper) causes most install pain&lt;/li&gt;
&lt;li&gt;Once installed correctly, daily Flutter work is simple&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  7. Final Verification
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter ✓&lt;/li&gt;
&lt;li&gt;Android toolchain ✓&lt;/li&gt;
&lt;li&gt;Xcode ✓ (after simulator install)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This guide reflects a &lt;strong&gt;real-world, non-ideal setup path&lt;/strong&gt;, which makes it far more valuable than a clean tutorial. You now understand not just &lt;em&gt;how&lt;/em&gt; to install Flutter — but &lt;em&gt;why it breaks&lt;/em&gt; and how to recover.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>tooling</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From Clone to iPhone: A First-Time Flutter iOS Journey (with All the Pitfalls)</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Tue, 10 Feb 2026 18:53:48 +0000</pubDate>
      <link>https://dev.to/cynthizo/from-clone-to-iphone-a-first-time-flutter-ios-journey-with-all-the-pitfalls-4ja8</link>
      <guid>https://dev.to/cynthizo/from-clone-to-iphone-a-first-time-flutter-ios-journey-with-all-the-pitfalls-4ja8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post documents my &lt;strong&gt;first real interaction with Flutter, Dart, Android Studio, Xcode, and iOS development&lt;/strong&gt;, starting from cloning an existing Flutter repository all the way to running the app on a real iPhone.&lt;/p&gt;

&lt;p&gt;It is intentionally &lt;strong&gt;long, explicit, and honest&lt;/strong&gt;. If you want a clean tutorial, this is not it. If you want a &lt;strong&gt;real-world reference you can redo from scratch and debug when things go wrong&lt;/strong&gt;, this is.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Context &amp;amp; Goal
&lt;/h2&gt;

&lt;p&gt;I did &lt;strong&gt;not&lt;/strong&gt; start this project to learn Flutter in isolation.&lt;/p&gt;

&lt;p&gt;The real goal was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Take an &lt;strong&gt;existing Flutter app&lt;/strong&gt; from GitHub&lt;/li&gt;
&lt;li&gt;Build and run it for &lt;strong&gt;Android&lt;/strong&gt; (already working)&lt;/li&gt;
&lt;li&gt;Compile and run it for &lt;strong&gt;iOS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Eventually share it with testers (students)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hardware &amp;amp; constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS (Apple Silicon)&lt;/li&gt;
&lt;li&gt;Physical Android device available&lt;/li&gt;
&lt;li&gt;iPhone available&lt;/li&gt;
&lt;li&gt;No Apple Developer paid account initially&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. The Repository
&lt;/h2&gt;

&lt;p&gt;The starting point was an existing Flutter repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/development
git clone https://github.com/Cyfamod-Technologies/school-app-flutter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;school-app-flutter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android version already ran&lt;/li&gt;
&lt;li&gt;iOS/macOS had never been built on this machine&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. First Run: macOS Desktop (Sanity Check)
&lt;/h2&gt;

&lt;p&gt;Before touching iOS, I verified the Flutter toolchain by running the macOS desktop target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter config &lt;span class="nt"&gt;--enable-macos-desktop&lt;/span&gt;
flutter pub get
flutter run &lt;span class="nt"&gt;-d&lt;/span&gt; macos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What happened
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The app &lt;strong&gt;built successfully&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A desktop window opened&lt;/li&gt;
&lt;li&gt;The UI rendered&lt;/li&gt;
&lt;li&gt;A connectivity warning appeared (from app logic, not Flutter)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key lesson
&lt;/h3&gt;

&lt;p&gt;Running on macOS does &lt;strong&gt;not&lt;/strong&gt; mean iOS. Flutter supports multiple targets from the same codebase, but &lt;strong&gt;each platform is a separate build pipeline&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Understanding the Tools (Critical Mental Model)
&lt;/h2&gt;

&lt;p&gt;Before continuing, this mental model is essential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flutter&lt;/strong&gt;: Orchestrator (decides what to build)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dart&lt;/strong&gt;: Application language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android Studio&lt;/strong&gt;: SDK manager &amp;amp; Android tooling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android SDK + Java&lt;/strong&gt;: Required for Android builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Xcode&lt;/strong&gt;: Required for &lt;em&gt;any&lt;/em&gt; iOS/macOS build&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CocoaPods&lt;/strong&gt;: iOS dependency manager&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flutter does &lt;strong&gt;not replace&lt;/strong&gt; Xcode. It &lt;em&gt;uses&lt;/em&gt; it.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Running on iOS Simulator
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Launch the simulator
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open &lt;span class="nt"&gt;-a&lt;/span&gt; Simulator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify Flutter sees it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter devices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Run the app on iOS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"iPhone 16e"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;iPhone simulator opened&lt;/li&gt;
&lt;li&gt;App launched&lt;/li&gt;
&lt;li&gt;Login screen worked&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key lesson
&lt;/h3&gt;

&lt;p&gt;The iOS simulator is &lt;strong&gt;excellent for learning and UI work&lt;/strong&gt;, but it does not enforce Apple’s distribution rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Attempting to Run on a Real iPhone
&lt;/h2&gt;

&lt;p&gt;This is where most first-time iOS developers hit the wall.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial error
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The app identifier "com.example.myApp" cannot be registered
No profiles for 'com.example.myApp' were found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What this actually means
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;com.example.*&lt;/code&gt; is a &lt;strong&gt;placeholder&lt;/strong&gt; bundle identifier&lt;/li&gt;
&lt;li&gt;Apple requires &lt;strong&gt;globally unique&lt;/strong&gt; bundle IDs for real devices&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fix: Set a unique Bundle ID
&lt;/h3&gt;

&lt;p&gt;In Xcode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open ios/Runner.xcworkspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;strong&gt;Runner&lt;/strong&gt; → &lt;strong&gt;Signing &amp;amp; Capabilities&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Change Bundle Identifier from:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   com.example.myApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to something unique, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   com.yourname.schoolapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Enable &lt;strong&gt;Automatically manage signing&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your &lt;strong&gt;Apple ID&lt;/strong&gt; as the Team (free account is enough)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  7. Installing on a Real iPhone (Debug Build)
&lt;/h2&gt;

&lt;p&gt;With the bundle ID fixed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plug in iPhone via USB&lt;/li&gt;
&lt;li&gt;Unlock and &lt;strong&gt;Trust This Computer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Developer Mode&lt;/strong&gt; on the iPhone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run from Xcode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the iPhone as destination&lt;/li&gt;
&lt;li&gt;Click ▶️ Run&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;App installed on the phone&lt;/li&gt;
&lt;li&gt;App launched successfully&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Important discovery
&lt;/h3&gt;

&lt;p&gt;When the USB cable was removed, the app &lt;strong&gt;stopped opening&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;expected behavior&lt;/strong&gt; with free Apple IDs.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Debug vs Standalone vs Distribution (The Big Confusion)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Debug build (what free Apple ID gives you)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Installed via Xcode&lt;/li&gt;
&lt;li&gt;Tied to the Mac&lt;/li&gt;
&lt;li&gt;Often requires USB / debugger&lt;/li&gt;
&lt;li&gt;Expires after ~7 days&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Standalone distribution (what users expect)
&lt;/h3&gt;

&lt;p&gt;Requires &lt;strong&gt;paid Apple Developer account&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TestFlight&lt;/li&gt;
&lt;li&gt;App Store&lt;/li&gt;
&lt;li&gt;Ad Hoc distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is &lt;strong&gt;no workaround&lt;/strong&gt; for this. This is an Apple platform rule, not a Flutter issue.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Why Running on a Real iPhone Still Mattered
&lt;/h2&gt;

&lt;p&gt;Even though the simulator worked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real device exposed Apple’s signing model&lt;/li&gt;
&lt;li&gt;Validated the project is technically viable&lt;/li&gt;
&lt;li&gt;Proved the app can run on actual hardware&lt;/li&gt;
&lt;li&gt;Surfaced distribution constraints early (good thing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simulator is for &lt;strong&gt;learning&lt;/strong&gt;. The real device is for &lt;strong&gt;validation&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Cleaning Up (Pausing Safely)
&lt;/h2&gt;

&lt;p&gt;Since iOS distribution was blocked by policy (not code):&lt;/p&gt;

&lt;p&gt;Safe actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shut down simulators&lt;/li&gt;
&lt;li&gt;Close Xcode&lt;/li&gt;
&lt;li&gt;Remove the app from the phone&lt;/li&gt;
&lt;li&gt;Disconnect USB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter&lt;/li&gt;
&lt;li&gt;Xcode&lt;/li&gt;
&lt;li&gt;Android SDK&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows resuming later without reinstallation.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Troubleshooting Reference (What Broke &amp;amp; How It Was Fixed)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ "dart is damaged and can’t be opened"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; macOS Gatekeeper quarantine&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xattr &lt;span class="nt"&gt;-dr&lt;/span&gt; com.apple.quarantine ~/development/flutter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ❌ Android SDK found but Flutter can’t accept licenses
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Missing &lt;code&gt;cmdline-tools&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Android command-line tools&lt;/li&gt;
&lt;li&gt;Ensure:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ❌ &lt;code&gt;JAVA_HOME&lt;/code&gt; invalid / sdkmanager fails
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Homebrew Java not registered with macOS&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-sfn&lt;/span&gt; /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk &lt;span class="se"&gt;\&lt;/span&gt;
/Library/Java/JavaVirtualMachines/openjdk-17.jdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  ❌ iOS app won’t install on real phone
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Placeholder bundle ID&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Set a unique bundle identifier in Xcode&lt;/p&gt;




&lt;h3&gt;
  
  
  ❌ App stops working when USB is unplugged
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Free Apple ID limitations&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Requires paid Apple Developer account for distribution&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Final Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Flutter is powerful, but &lt;strong&gt;platform rules still apply&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;iOS development is as much about &lt;strong&gt;policy&lt;/strong&gt; as code&lt;/li&gt;
&lt;li&gt;The simulator is enough to learn&lt;/li&gt;
&lt;li&gt;Real devices are required to ship&lt;/li&gt;
&lt;li&gt;Apple’s $99 fee is not optional for real distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hitting these problems early is a sign you’re doing real development, not toy tutorials.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  13. What I Would Do Next (If Starting Again)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Learn UI &amp;amp; logic on iOS Simulator&lt;/li&gt;
&lt;li&gt;Validate once on a real iPhone&lt;/li&gt;
&lt;li&gt;Decide early whether TestFlight is required&lt;/li&gt;
&lt;li&gt;Budget Apple Developer account if yes&lt;/li&gt;
&lt;li&gt;Ship calmly&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;This document exists so I can &lt;strong&gt;redo this entire process from scratch without guessing&lt;/strong&gt;, and so other developers can avoid losing days to the same hidden traps.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devjournal</category>
      <category>flutter</category>
      <category>ios</category>
    </item>
    <item>
      <title>Cloning Private GitHub Repositories on a Server (The Right Way, With SSH)</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Sat, 31 Jan 2026 08:05:57 +0000</pubDate>
      <link>https://dev.to/cynthizo/cloning-private-github-repositories-on-a-server-the-right-way-with-ssh-3ib1</link>
      <guid>https://dev.to/cynthizo/cloning-private-github-repositories-on-a-server-the-right-way-with-ssh-3ib1</guid>
      <description>&lt;p&gt;At some point, most developers hit this wall:&lt;/p&gt;

&lt;p&gt;You’re logged into a server, you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:your-org/your-repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and Git responds with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Permission denied (publickey).
fatal: Could not read from remote repository.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You &lt;em&gt;know&lt;/em&gt; the repo exists.&lt;br&gt;
You &lt;em&gt;know&lt;/em&gt; you have access.&lt;br&gt;
So what gives?&lt;/p&gt;

&lt;p&gt;This post walks through &lt;strong&gt;why this happens&lt;/strong&gt;, and how to fix it &lt;strong&gt;cleanly and securely&lt;/strong&gt;, without leaking credentials or cutting corners.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Core Problem (In Plain English)
&lt;/h2&gt;

&lt;p&gt;GitHub doesn’t care &lt;em&gt;who you are&lt;/em&gt;.&lt;br&gt;
It cares &lt;strong&gt;which SSH key you’re presenting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;git clone&lt;/code&gt; from a server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub sees an SSH key&lt;/li&gt;
&lt;li&gt;It checks whether that key is authorized&lt;/li&gt;
&lt;li&gt;If it doesn’t recognize it → access denied&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. No magic.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: Understand What Machine You’re On
&lt;/h2&gt;

&lt;p&gt;There are &lt;strong&gt;three identities&lt;/strong&gt; involved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your laptop&lt;/strong&gt; (already authenticated with GitHub)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your server&lt;/strong&gt; (a completely different machine)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Linux user on that server&lt;/strong&gt; (e.g. &lt;code&gt;app&lt;/code&gt;, &lt;code&gt;bot&lt;/code&gt;, or &lt;code&gt;deploy&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the repo is yours&lt;/li&gt;
&lt;li&gt;your laptop has access&lt;/li&gt;
&lt;li&gt;you’re “the same person”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;The server has its own identity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You must explicitly grant it access.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Generate an SSH Key on the Server
&lt;/h2&gt;

&lt;p&gt;Log into the server as the user that will run the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh app@your-server-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate a new SSH key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"app-server-repo-access"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Press &lt;strong&gt;Enter&lt;/strong&gt; to accept the default location&lt;/li&gt;
&lt;li&gt;Leave the passphrase empty (recommended for non-interactive servers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.ssh/id_ed25519        (private key)
~/.ssh/id_ed25519.pub    (public key)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Copy the Public Key
&lt;/h2&gt;

&lt;p&gt;Display the public key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see a single line starting with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy &lt;strong&gt;the entire line&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Add the Key to GitHub (Deploy Key Method)
&lt;/h2&gt;

&lt;p&gt;This is the &lt;strong&gt;recommended approach for servers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In GitHub:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the repository&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Deploy keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add deploy key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name (e.g. &lt;code&gt;app-server&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Paste the public key&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;write access&lt;/strong&gt; only if the server needs to push&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This key now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;works &lt;strong&gt;only for this repo&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;cannot access anything else&lt;/li&gt;
&lt;li&gt;is safe to revoke anytime&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 5: Test SSH Connectivity
&lt;/h2&gt;

&lt;p&gt;Back on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-T&lt;/span&gt; git@github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If successful, you’ll see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hi! You've successfully authenticated, but GitHub does not provide shell access.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s a good thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Clone the Repository
&lt;/h2&gt;

&lt;p&gt;Now retry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:your-org/your-repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, it should work without errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Pitfall: “But I Can Clone From My Laptop”
&lt;/h2&gt;

&lt;p&gt;That’s expected.&lt;/p&gt;

&lt;p&gt;Your laptop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;has its own SSH key&lt;/li&gt;
&lt;li&gt;already registered with GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is a different machine&lt;/li&gt;
&lt;li&gt;needs its &lt;em&gt;own&lt;/em&gt; key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SSH access is &lt;strong&gt;machine-based&lt;/strong&gt;, not user-based.&lt;/p&gt;




&lt;h2&gt;
  
  
  Alternative: HTTPS (When It Makes Sense)
&lt;/h2&gt;

&lt;p&gt;If the repo is public, you can always do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-org/your-repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For private repos, HTTPS requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a GitHub token&lt;/li&gt;
&lt;li&gt;storing credentials on the server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For long-running services, &lt;strong&gt;SSH deploy keys are cleaner and safer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Setup Is Best Practice
&lt;/h2&gt;

&lt;p&gt;Using deploy keys means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no shared credentials&lt;/li&gt;
&lt;li&gt;no copying laptop keys to servers&lt;/li&gt;
&lt;li&gt;repo-scoped access&lt;/li&gt;
&lt;li&gt;easy rotation and revocation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how CI systems, deployment bots, and production servers are meant to work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;git clone&lt;/code&gt; fails on a server, it’s almost never a Git issue.&lt;/p&gt;

&lt;p&gt;It’s an &lt;strong&gt;identity issue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once you understand that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Servers authenticate themselves to GitHub using SSH keys&lt;/em&gt;,&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the fix becomes straightforward, secure, and repeatable.&lt;/p&gt;

&lt;p&gt;Happy deploying 🚀&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
      <category>github</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SSH Like a Pro: Creating an Isolated User on an EC2 Instance (Without Breaking Anything)</title>
      <dc:creator>cynthia wahome</dc:creator>
      <pubDate>Sat, 31 Jan 2026 08:05:09 +0000</pubDate>
      <link>https://dev.to/cynthizo/ssh-like-a-pro-creating-an-isolated-user-on-an-ec2-instance-without-breaking-anything-3576</link>
      <guid>https://dev.to/cynthizo/ssh-like-a-pro-creating-an-isolated-user-on-an-ec2-instance-without-breaking-anything-3576</guid>
      <description>&lt;p&gt;If you’ve ever deployed more than one project on a single server, you’ve probably felt this tension:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I don’t want this new thing to interfere with what’s already running.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s exactly the situation I found myself in.&lt;/p&gt;

&lt;p&gt;I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an existing EC2 instance&lt;/li&gt;
&lt;li&gt;a main user running a larger project&lt;/li&gt;
&lt;li&gt;and a new, smaller service I wanted to deploy &lt;strong&gt;cleanly and safely&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution wasn’t Docker or Kubernetes.&lt;/p&gt;

&lt;p&gt;It was something much simpler — &lt;strong&gt;Linux users + SSH keys&lt;/strong&gt;, done properly.&lt;/p&gt;

&lt;p&gt;This post walks through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why creating a separate user matters&lt;/li&gt;
&lt;li&gt;how SSH authentication &lt;em&gt;actually&lt;/em&gt; works&lt;/li&gt;
&lt;li&gt;and how to log in as a new user &lt;strong&gt;securely, without passwords&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;I wanted to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a new Linux user (&lt;code&gt;cc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;isolate a new project under that user&lt;/li&gt;
&lt;li&gt;log in directly as &lt;code&gt;cc&lt;/code&gt; using SSH&lt;/li&gt;
&lt;li&gt;keep everything secure and professional&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No hacks. No shortcuts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Create a New User
&lt;/h2&gt;

&lt;p&gt;First, SSH into the server as your existing user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh your-current-user@your-ec2-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a new user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;adduser cc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be prompted to set a password and optional user details.&lt;br&gt;
You can safely skip the extra fields.&lt;/p&gt;

&lt;p&gt;Optionally (but recommended), give the user sudo access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;cc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, &lt;code&gt;cc&lt;/code&gt; exists — but you &lt;strong&gt;cannot SSH into it yet&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Understand What SSH Actually Authenticates
&lt;/h2&gt;

&lt;p&gt;This is where most confusion happens.&lt;/p&gt;

&lt;p&gt;SSH does &lt;strong&gt;not&lt;/strong&gt; authenticate repositories.&lt;br&gt;
It does &lt;strong&gt;not&lt;/strong&gt; authenticate servers.&lt;br&gt;
It authenticates &lt;strong&gt;users&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;More specifically:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SSH proves that &lt;strong&gt;you own a private key&lt;/strong&gt;, and the server checks whether the &lt;strong&gt;matching public key&lt;/strong&gt; is allowed to log in as a specific Linux user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each user has their own allowlist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a public key is not listed there → no login.&lt;/p&gt;

&lt;p&gt;A brand-new user like &lt;code&gt;cc&lt;/code&gt; has an &lt;strong&gt;empty allowlist&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Identify the Correct SSH Key
&lt;/h2&gt;

&lt;p&gt;On your local machine, test your existing SSH connection with verbosity enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-v&lt;/span&gt; your-current-user@your-ec2-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for a line like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offering public key: ~/.ssh/backend-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which private key your laptop is using&lt;/li&gt;
&lt;li&gt;and therefore which public key represents &lt;em&gt;you&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The matching public key is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.ssh/backend-key.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;only key&lt;/strong&gt; you should use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Allow That Key to Log In as &lt;code&gt;cc&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;On the server, create the SSH directory for the new user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/cc/.ssh
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;700 /home/cc/.ssh
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;cc:cc /home/cc/.ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create the authorization file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /home/cc/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the &lt;strong&gt;entire contents&lt;/strong&gt; of your local &lt;code&gt;backend-key.pub&lt;/code&gt; file into it.&lt;/p&gt;

&lt;p&gt;Then fix permissions (this part is critical):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;cc:cc /home/cc/.ssh/authorized_keys
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /home/cc/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Log In as the New User
&lt;/h2&gt;

&lt;p&gt;From your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh cc@your-ec2-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is set up correctly, you’re in 🎉&lt;/p&gt;

&lt;p&gt;You can confirm with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;whoami
pwd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cc
/home/cc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  “Why Can I Log In Without a Password?”
&lt;/h2&gt;

&lt;p&gt;This surprises a lot of people — but it’s actually the point.&lt;/p&gt;

&lt;p&gt;You are not logging in “without authentication”.&lt;br&gt;
You are authenticating with &lt;strong&gt;cryptography&lt;/strong&gt;, not passwords.&lt;/p&gt;

&lt;p&gt;SSH keys are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resistant to brute force&lt;/li&gt;
&lt;li&gt;resistant to phishing&lt;/li&gt;
&lt;li&gt;industry standard in cloud environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is &lt;strong&gt;more secure&lt;/strong&gt; than password-based SSH.&lt;/p&gt;

&lt;p&gt;If you want extra protection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;disable password SSH entirely&lt;/li&gt;
&lt;li&gt;keep passwords for &lt;code&gt;sudo&lt;/code&gt; only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s how most production systems are set up.&lt;/p&gt;


&lt;h2&gt;
  
  
  Optional: Clean Up Your Local SSH Config
&lt;/h2&gt;

&lt;p&gt;To keep things tidy, you can name this connection in &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;-&lt;span class="n"&gt;ec2&lt;/span&gt;
    &lt;span class="n"&gt;HostName&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;ec2&lt;/span&gt;-&lt;span class="n"&gt;ip&lt;/span&gt;
    &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;
    &lt;span class="n"&gt;IdentityFile&lt;/span&gt; ~/.&lt;span class="n"&gt;ssh&lt;/span&gt;/&lt;span class="n"&gt;backend&lt;/span&gt;-&lt;span class="n"&gt;key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh cc-ec2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear, explicit, and hard to mess up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Setup Is Worth It
&lt;/h2&gt;

&lt;p&gt;By doing this, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full isolation between projects&lt;/li&gt;
&lt;li&gt;separate environments and dependencies&lt;/li&gt;
&lt;li&gt;smaller blast radius if something breaks&lt;/li&gt;
&lt;li&gt;a setup that scales with you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You didn’t just “make SSH work”.&lt;/p&gt;

&lt;p&gt;You set up your server the way professionals do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This entire process has &lt;strong&gt;nothing to do with Git repos&lt;/strong&gt; and everything to do with &lt;strong&gt;identity and trust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once that mental model clicks, SSH stops feeling magical — and starts feeling solid.&lt;/p&gt;

&lt;p&gt;If you run multiple services on one server, this pattern will serve you for years.&lt;/p&gt;

&lt;p&gt;Happy hacking 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>linux</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
