<?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: Jessica Temporal</title>
    <description>The latest articles on DEV Community by Jessica Temporal (@jesstemporal).</description>
    <link>https://dev.to/jesstemporal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F238241%2F6069ae7c-7b26-40f6-ad35-31377af2d653.jpeg</url>
      <title>DEV Community: Jessica Temporal</title>
      <link>https://dev.to/jesstemporal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jesstemporal"/>
    <language>en</language>
    <item>
      <title>Recovering lost commits with git reflog</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Sat, 20 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/jesstemporal/recovering-lost-commits-with-git-reflog-3d3o</link>
      <guid>https://dev.to/jesstemporal/recovering-lost-commits-with-git-reflog-3d3o</guid>
      <description>&lt;p&gt;There is a particular kind of panic that hits when a commit looks like it just vanished. Maybe you ran a hard reset, or a rebase went sideways, and the work you had a minute ago is nowhere in your history. Before you assume it is gone for good, there is one command that may be able to bring it back: &lt;code&gt;git reflog&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the reflog?
&lt;/h2&gt;

&lt;p&gt;Git keeps a private log of everywhere HEAD has been. Everything you do in Git that moves &lt;a href="https://jtemporal.com/undoing-the-last-commits-using-git-reset/" rel="noopener noreferrer"&gt;the HEAD pointer&lt;/a&gt; is recorded, whether you made a commit, did a checkout, ran a reset, or a rebase or merge moved it around.&lt;/p&gt;

&lt;p&gt;The log of that movement lives in the reference log (reflog for short), and it is local to your machine. So even when a commit no longer shows up when you do &lt;code&gt;git log&lt;/code&gt;, the reference to it usually still lives in the reflog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the commit you lost
&lt;/h2&gt;

&lt;p&gt;To see the reference log you can run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a list of recent moves, with the most recent entry at the top, like shown in the following image.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2e1biywz8xy9ujvaacwv.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2e1biywz8xy9ujvaacwv.webp" alt="git reflog output showing recent HEAD movements" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each line has a short hash, a reference like &lt;code&gt;HEAD@{2}&lt;/code&gt;, and a description of what happened, things like “commit”, “reset: moving to”, or “checkout”.&lt;/p&gt;

&lt;p&gt;Even though it may be hard to get used to reading this new format of log, the description and order usually make it clear where you were and can be the indication you need to know where you want to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting back to it
&lt;/h2&gt;

&lt;p&gt;Once you have spotted the right entry, copy its hash. Then move your branch back to that point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;hash&amp;gt;&lt;/code&gt; with the hash you found in the reflog.&lt;/p&gt;

&lt;p&gt;For example, thinking of the image above, if you want to go to the state before the reset you’d need to run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; a68cffa

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after that, your working state matches the moment before the mistake, like it never happened.&lt;/p&gt;

&lt;p&gt;A quick note of caution: &lt;code&gt;git reset --hard&lt;/code&gt; discards your current uncommitted changes, so make sure you actually want to land on that older state before you run it. If you are not sure, you can use &lt;code&gt;git switch&lt;/code&gt; to the hash first to look around without moving your branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Most people learn about the reflog the hard way after losing the work to a reset or a rebase, so knowing it exists can be extremely helpful in a pinch.&lt;/p&gt;

&lt;p&gt;Here’s a recap of the steps to restore a previous lost state with reflog:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the state you want to go back to, and copy its hash.&lt;/li&gt;
&lt;li&gt;Check if that’s the actual state you want to restore with &lt;code&gt;git switch &amp;lt;hash&amp;gt;&lt;/code&gt;. If it’s not, repeat steps 1 and 2 until you find the correct state.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git reset --hard &amp;lt;hash&amp;gt;&lt;/code&gt; to get back to it.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>git</category>
      <category>english</category>
      <category>protip</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Recuperando commits perdidos com git reflog</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/jesstemporal/recuperando-commits-perdidos-com-git-reflog-51hp</link>
      <guid>https://dev.to/jesstemporal/recuperando-commits-perdidos-com-git-reflog-51hp</guid>
      <description>&lt;p&gt;Tem um tipo específico de pânico que bate quando parece que um commit simplesmente sumiu. Talvez você tenha rodado um hard reset, ou um rebase deu errado, e o trabalho que estava aí um minuto atrás não aparece em lugar nenhum do seu histórico. Antes de assumir que ele foi embora para sempre, tem um comando que pode te ajudar a trazer isso de volta: &lt;code&gt;git reflog&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é o reflog?
&lt;/h2&gt;

&lt;p&gt;O Git mantém um registro privado de todos os lugares por onde o HEAD passou. Tudo o que você faz no Git que move &lt;a href="https://dev.to/desfazendo-um-ou-mais-commits/"&gt;o ponteiro HEAD&lt;/a&gt; fica registrado, seja porque você fez um commit, deu um checkout, rodou um reset, ou porque um rebase ou merge mexeu nele.&lt;/p&gt;

&lt;p&gt;Esse registro de movimento fica no log de referência (do inglês &lt;em&gt;reference log&lt;/em&gt; ou reflog, para encurtar) e ele é local na sua máquina. Então, mesmo quando um commit não aparece mais no &lt;code&gt;git log&lt;/code&gt;, a referência para ele geralmente ainda está no reflog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encontrando o commit que você perdeu
&lt;/h2&gt;

&lt;p&gt;Para ver o reference log, você pode rodar o comando abaixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você vai ver uma lista de movimentos recentes, com a entrada mais recente no topo, como na imagem abaixo.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2e1biywz8xy9ujvaacwv.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2e1biywz8xy9ujvaacwv.webp" alt="saída do git reflog mostrando movimentos recentes do HEAD" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cada linha tem um hash curto, uma referência como &lt;code&gt;HEAD@{2}&lt;/code&gt;, e uma descrição do que aconteceu, coisas como “commit”, “reset: moving to” ou “checkout”.&lt;/p&gt;

&lt;p&gt;Mesmo que seja difícil se acostumar a ler esse novo formato de log, a descrição e a ordem geralmente deixam claro onde você estava e podem ser a indicação que você precisa para saber para onde quer voltar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voltando para lá
&lt;/h2&gt;

&lt;p&gt;Quando você encontrar a entrada certa, copie o &lt;em&gt;hash&lt;/em&gt;. Depois, mova o seu branch de volta para aquele ponto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Substitua &lt;code&gt;&amp;lt;hash&amp;gt;&lt;/code&gt; pelo hash que você encontrou no reflog.&lt;/p&gt;

&lt;p&gt;Por exemplo, pensando na imagem acima, se você quiser voltar para o estado antes do reset, você precisaria rodar o seguinte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; a68cffa

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois disso, o seu estado de trabalho volta a bater com o momento antes do erro, como se nada tivesse acontecido.&lt;/p&gt;

&lt;p&gt;Uma observação rápida: &lt;code&gt;git reset --hard&lt;/code&gt; descarta as suas mudanças não commitadas. Então tenha certeza de que você realmente quer cair nesse estado anterior antes de rodar o comando. Se você não tiver certeza, você pode usar &lt;code&gt;git switch&lt;/code&gt; para ir até o hash primeiro e dar uma olhada sem mover o seu branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recapitulando
&lt;/h2&gt;

&lt;p&gt;A maioria das pessoas aprende sobre o reflog do jeito difícil, depois de perder trabalho por causa de um reset ou de um rebase. Então saber que ele existe pode ser uma mão na roda numa emergência.&lt;/p&gt;

&lt;p&gt;Recapitulando os passos para restaurar um estado anterior perdido com o reflog:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encontre o estado para o qual você quer voltar e copie o hash.&lt;/li&gt;
&lt;li&gt;Confira se esse é mesmo o estado que você quer restaurar com &lt;code&gt;git switch &amp;lt;hash&amp;gt;&lt;/code&gt;. Se não for, repita os passos 1 e 2 até encontrar o estado certo.&lt;/li&gt;
&lt;li&gt;Rode &lt;code&gt;git reset --hard &amp;lt;hash&amp;gt;&lt;/code&gt; para voltar para ele.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>git</category>
      <category>português</category>
      <category>colinha</category>
    </item>
    <item>
      <title>agenticstack.sh: compare tools for building AI agents</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 19 Jun 2026 03:00:00 +0000</pubDate>
      <link>https://dev.to/jesstemporal/agenticstacksh-compare-tools-for-building-ai-agents-1l07</link>
      <guid>https://dev.to/jesstemporal/agenticstacksh-compare-tools-for-building-ai-agents-1l07</guid>
      <description>&lt;p&gt;If you are building AI agents and wondering which tools to use, &lt;a href="https://agenticstack.sh" rel="noopener noreferrer"&gt;agenticstack.sh&lt;/a&gt; is worth bookmarking. 🔖&lt;/p&gt;

&lt;p&gt;This website lets you compare tools like auth, email, hosting, CMS, and others, so you’ll pick the best one for your use case.&lt;/p&gt;

&lt;p&gt;It has comparative tables you can use to make an informed decision.&lt;/p&gt;

&lt;p&gt;And since we are talking about AI here, it even has skills that you can install so you can ask questions right in your AI assistant chat. 😄&lt;/p&gt;


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/R7nI0qm8OA4"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;


&lt;p&gt;Let me know if you try it!&lt;/p&gt;

&lt;p&gt;Follow along for more tips like this!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>short</category>
      <category>devtools</category>
    </item>
    <item>
      <title>git stash: save your work without committing</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Thu, 18 Jun 2026 03:00:00 +0000</pubDate>
      <link>https://dev.to/jesstemporal/git-stash-save-your-work-without-committing-3he0</link>
      <guid>https://dev.to/jesstemporal/git-stash-save-your-work-without-committing-3he0</guid>
      <description>&lt;p&gt;You’re working, some priority comes up, but you’re not ready to make a commit yet, and you need to switch branches. What do you do? 🤔&lt;/p&gt;

&lt;p&gt;Some people like to make a throwaway commit just to save the work so they can undo it later, but I prefer &lt;code class="language-plaintext highlighter-rouge"&gt;git stash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Think of it like a stack. You take the changes you made, bundle them up, save them locally, and then your working directory goes back to a clean state.&lt;/p&gt;

&lt;p&gt;Each bundle is a stash and it gets saved on index zero.&lt;/p&gt;

&lt;p&gt;You can then switch branches, do whatever you need to do, and when you come back, you can &lt;code class="language-plaintext highlighter-rouge"&gt;pop&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;apply&lt;/code&gt; the stash, and your most recent changes go back to where they were.&lt;/p&gt;

&lt;p&gt;One heads up though: if you have new files that git isn’t tracking yet, they won’t be automatically added to this stash. So before you make a stash with new files, you need to stage them first. 😅&lt;/p&gt;

&lt;p&gt;Want the full breakdown on git stash? I covered it in more depth here: &lt;a href="/using-git-stash-and-git-stash-pop/"&gt;Using git stash: pop, apply, and drop&lt;/a&gt;&lt;/p&gt;


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/rnAiEg_si8s"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;


&lt;p&gt;If you want more git tips that save your work, follow along!&lt;/p&gt;

</description>
      <category>english</category>
      <category>git</category>
      <category>protip</category>
      <category>short</category>
    </item>
    <item>
      <title>Preview Auth0 Changes Safely with Deploy CLI Dry Run</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Tue, 16 Jun 2026 22:25:31 +0000</pubDate>
      <link>https://dev.to/auth0/preview-auth0-changes-safely-with-deploy-cli-dry-run-38hn</link>
      <guid>https://dev.to/auth0/preview-auth0-changes-safely-with-deploy-cli-dry-run-38hn</guid>
      <description>&lt;p&gt;Managing your Auth0 tenant configuration as code shouldn't be a "guess and check" process. In this video, I'll demonstrate how to use the &lt;em&gt;Auth0 Deploy CLI's Dry Run&lt;/em&gt; feature. Learn how to preview exactly which resources will be created, updated, or deleted before you ever touch your production environment.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What You'll Learn&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to configure the Auth0 Deploy CLI with a &lt;code&gt;config.json&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;The proper syntax for exporting your current tenant configuration to YAML.&lt;/li&gt;
&lt;li&gt;How to use the &lt;code&gt;--dry-run&lt;/code&gt; flag to validate local changes.&lt;/li&gt;
&lt;li&gt;Best practices for keeping sensitive credentials out of your Git history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Resources&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📄 &lt;a href="https://github.com/auth0/auth0-deploy-cli/blob/master/docs/using-dry-run.md" rel="noopener noreferrer"&gt;Auth0 Deploy CLI Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/auth0/auth0-deploy-cli/tree/master" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tooling</category>
      <category>ai</category>
      <category>security</category>
      <category>cli</category>
    </item>
    <item>
      <title>How to Decode, Encode, and Validate JWTs inside Claude Code</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 29 May 2026 17:42:15 +0000</pubDate>
      <link>https://dev.to/auth0/how-to-decode-encode-and-validate-jwts-inside-claude-code-240b</link>
      <guid>https://dev.to/auth0/how-to-decode-encode-and-validate-jwts-inside-claude-code-240b</guid>
      <description>&lt;p&gt;&lt;em&gt;Stop context switching and start debugging.&lt;/em&gt; Let me show you how to supercharge your AI agent with JWT Skills. Learn how to decode, encode, and validate JSON Web Tokens (JWTs) directly within your terminal session using Claude Code. Whether you're troubleshooting an expired token or testing a custom claim, these tools bring security insights right into your development flow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What You'll Learn:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to install the &lt;code&gt;jwt-skills&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Decoding tokens to inspect headers, payloads, and claims.&lt;/li&gt;
&lt;li&gt;Identifying security flags like expired tokens or PII in payloads.&lt;/li&gt;
&lt;li&gt;Generating test tokens with custom claims for edge-case testing.&lt;/li&gt;
&lt;li&gt;Validating tokens against JWKS endpoints for production-level checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Resources:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🛠 &lt;em&gt;&lt;a href="https://github.com/jsonwebtoken/jwt-skills" rel="noopener noreferrer"&gt;Get the Skills&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;em&gt;&lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;JWT Deep Dive&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Manage Your Auth0 Tenants Faster with the Gemini CLI Extension</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Wed, 29 Apr 2026 21:01:35 +0000</pubDate>
      <link>https://dev.to/auth0/manage-your-auth0-tenants-faster-with-the-gemini-cli-extension-2dh8</link>
      <guid>https://dev.to/auth0/manage-your-auth0-tenants-faster-with-the-gemini-cli-extension-2dh8</guid>
      <description>&lt;p&gt;Stop switching between your browser and terminal to manage identity. In this video, I'll show you how to integrate the &lt;em&gt;Auth0 MCP Server&lt;/em&gt; directly into your &lt;em&gt;Gemini CLI&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Learn how to leverage AI to query your applications, initialize tenants, and manage APIs using natural language commands without leaving your development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to find the Auth0 extension on the official Gemini page.&lt;/li&gt;
&lt;li&gt;The one-command installation process for the Auth0 MCP server.&lt;/li&gt;
&lt;li&gt;Authenticating and initializing your Auth0 environment via CLI.&lt;/li&gt;
&lt;li&gt;Using natural language to list applications and create new API resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/auth0/auth0-mcp-server" rel="noopener noreferrer"&gt;💻 Auth0 MCP Server GitHub Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/docs/get-started/auth0-mcp-server" rel="noopener noreferrer"&gt;🛠️ Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/auth0-mcp-server-in-gemini-cli-extensions" rel="noopener noreferrer"&gt;📝 Auth0 Blog Post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tooling</category>
      <category>gemini</category>
    </item>
    <item>
      <title>How to Use Auth0 Agent Skills in Claude Code &amp; AI Coding Assistants</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Mon, 06 Apr 2026 18:27:59 +0000</pubDate>
      <link>https://dev.to/auth0/how-to-use-auth0-agent-skills-in-claude-code-ai-coding-assistants-56e5</link>
      <guid>https://dev.to/auth0/how-to-use-auth0-agent-skills-in-claude-code-ai-coding-assistants-56e5</guid>
      <description>&lt;p&gt;Tired of your AI coding assistant hallucinating APIs or writing insecure auth patterns? In this video, I'll show you how to use Auth0 Agent Skills to teach your AI assistant (like Claude Code or GitHub Copilot) how to implement Auth0 correctly. Say goodbye to XSS vulnerabilities and manual JWT decoding—ship production-ready, secure authentication from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why standard AI coding assistants struggle with secure authentication.&lt;/li&gt;
&lt;li&gt;How to install Auth0 Agent Skills via NPX or Claude Code plugins.&lt;/li&gt;
&lt;li&gt;The difference between Core Skills and SDK Skills (React, Next.js, etc.).&lt;/li&gt;
&lt;li&gt;A side-by-side comparison of "hallucinated" code vs. secure Auth0 patterns.&lt;/li&gt;
&lt;li&gt;How to implement production-ready auth in Next.js using Agent Skills.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;📝 &lt;a href="https://auth0.com/blog/auth0-agent-skills-ai-coding-assistants/" rel="noopener noreferrer"&gt;Read the full blog post&lt;/a&gt;&lt;br&gt;
🛠️ &lt;a href="https://auth0.com/docs/quickstart/agent-skills" rel="noopener noreferrer"&gt;Auth0 Documentation&lt;/a&gt;&lt;br&gt;
💻 &lt;a href="https://github.com/auth0/agent-skills" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Auth0 MCP Server Extension for Gemini CLI</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 27 Mar 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/auth0/auth0-mcp-server-extension-for-gemini-cli-405m</link>
      <guid>https://dev.to/auth0/auth0-mcp-server-extension-for-gemini-cli-405m</guid>
      <description>&lt;p&gt;The Auth0 MCP Server is now listed on the official Gemini CLI extensions page. This means the Auth0 MCP Server is now directly installable through Gemini CLI with one command, allowing you to authenticate to Auth0 directly from your Gemini CLI session and load tenant information automatically.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iiSuv8BnPC0"&gt;
  &lt;/iframe&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  What the Auth0 MCP Server Extension Provides
&lt;/h2&gt;

&lt;p&gt;The extension packages the Auth0 MCP Server for Gemini CLI and adds three integration layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discoverability&lt;/strong&gt;: Listed on &lt;a href="https://geminicli.com/extensions" rel="noopener noreferrer"&gt;geminicli.com/extensions&lt;/a&gt;, searchable by name, installable without manual configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication Commands&lt;/strong&gt;: Built-in slash commands for Auth0 tenant management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/auth0:init&lt;/code&gt; - Device authorization flow with tenant selection&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/auth0:logout&lt;/code&gt; - Session termination&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/auth0:session&lt;/code&gt; - Current authentication status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Context Injection&lt;/strong&gt;: After authentication, Gemini gains your tenant information so the AI can query applications, APIs, connections, actions, and logs without requiring manual tenant specification in every prompt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;

&lt;p&gt;Install the extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini extensions &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/auth0/auth0-mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the command successfully finishes you should see a message stating &lt;code&gt;Extension “Auth0” installed successfully&lt;/code&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%2Ffzi13ha0j201bszply11.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%2Ffzi13ha0j201bszply11.png" alt="Terminal showing the installation of the Auth0 MCP server as a Gemini CLI extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initialize the Auth0 MCP Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/auth0:init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to allow the command to run when prompted. The server will run automatically.&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%2Fr7q3o4dmnm02utqeu4ji.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%2Fr7q3o4dmnm02utqeu4ji.png" alt="Gemini CLI terminal showing /auth0:init command with permission prompt to allow command execution"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll authenticate via &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" rel="noopener noreferrer"&gt;device code flow&lt;/a&gt; to select your tenant:&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%2Frnik2h6j061y0y9brksu.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%2Frnik2h6j061y0y9brksu.png" alt="Auth0 device authorization screen displaying device code and instructions to complete authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And confirm the permissions:&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%2F0wx8dzfzrmbnntzeaj7b.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%2F0wx8dzfzrmbnntzeaj7b.png" alt="Auth0 authorization screen showing requested permissions for Auth0 MCP Server extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once authenticated, you should see a message within Gemini saying the Auth0 MCP Server is configured and to restart Gemini CLI to see the changes:&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%2F10jdvkxmdmkufvebcm26.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%2F10jdvkxmdmkufvebcm26.png" alt="Gemini CLI terminal displaying successful Auth0 MCP Server initialization with tenant connection confirmed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refresh the MCP server list with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/mcp refresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemini now has Auth0 context. Ask: "show me my applications" and the AI will receive the structured information about your applications, which Gemini CLI will display as a structured tool call result:&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%2Fwrab4gmufeqlx8rtwf8m.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%2Fwrab4gmufeqlx8rtwf8m.png" alt="Gemini CLI tool call output showing structured JSON data of Auth0 applications from the MCP server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And since Gemini now understands your tenant structure, existing configurations, and naming conventions, it can also show you the same information in a more readable format:&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%2F7n6pl8wgu6ipiazokouq.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%2F7n6pl8wgu6ipiazokouq.png" alt="Gemini CLI displaying formatted, human-readable list of Auth0 applications with names, types, and client IDs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Before this extension, using the Auth0 MCP Server with Gemini CLI required manual server configuration, environment variable setup, and custom initialization scripts. The extension collapses that into a single install command and three slash commands.&lt;/p&gt;

&lt;p&gt;More importantly: context persistence. Once authenticated, every Gemini session knows your Auth0 environment. You're not re-explaining your tenant structure or copy-pasting app IDs. The AI assistant operates with the same tenant awareness you have.&lt;/p&gt;

&lt;p&gt;This is the same Auth0 MCP Server that powers VS Code integrations, now packaged for Gemini CLI's extension model. Same capabilities, different CLI.&lt;/p&gt;

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

&lt;p&gt;The Auth0 MCP Server supports tenant management, application configuration, API setup, and log analysis. For implementation details and the full MCP Server feature set, &lt;a href="https://github.com/auth0/auth0-mcp-server?tab=readme-ov-file#%EF%B8%8F-supported-tools" rel="noopener noreferrer"&gt;see the list on GitHub here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The extension is available now at &lt;a href="https://geminicli.com/extensions" rel="noopener noreferrer"&gt;geminicli.com/extensions&lt;/a&gt;. Install, authenticate, and start managing Auth0 tenants through natural language.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Demystifying OAuth Security: State vs. Nonce vs. PKCE</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 13 Mar 2026 16:17:44 +0000</pubDate>
      <link>https://dev.to/auth0/demystifying-oauth-security-state-vs-nonce-vs-pkce-2eo2</link>
      <guid>https://dev.to/auth0/demystifying-oauth-security-state-vs-nonce-vs-pkce-2eo2</guid>
      <description>&lt;p&gt;Confused by the random strings in your OAuth URLs? You aren't alone. Many developers think &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, and &lt;code&gt;code_challenge&lt;/code&gt; (PKCE) are redundant—but skipping just one could leave your users' accounts wide open to attackers like "Eve." In this video, I'll break down why these three parameters are like three different locks on three different doors. We’ll look at real-world attack scenarios and show you exactly how each one keeps your app secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡  What You’ll Learn:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The State Parameter:&lt;/strong&gt; How to prevent Cross-Site Request Forgery ($CSRF$) attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Nonce Parameter:&lt;/strong&gt; Why ID tokens need protection against Replay attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PKCE (Proof Key for Code Exchange):&lt;/strong&gt; Protecting mobile and single-page apps from Authorization Code Injection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementation Strategy:&lt;/strong&gt; Why you should use all three instead of picking just one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://auth0.com/blog/demystifying-oauth-security-state-vs-nonce-vs-pkce" rel="noopener noreferrer"&gt;Read the full blog post by Andrea Chiarelli&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce" rel="noopener noreferrer"&gt;Auth0 Docs - Why PKCE?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://oauth.net/2/security-topics/" rel="noopener noreferrer"&gt;OAuth 2.0 Security Best Practices&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you enjoy this content and want to learn more about identity, security, and access management, subscribe to our channel! &lt;/p&gt;

&lt;p&gt;Have a topic you'd like to see covered? Let us know if the comments below 👀 &lt;/p&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>software</category>
    </item>
    <item>
      <title>Automating CSS versioning in staging through GitHub Actions</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Tue, 03 Feb 2026 06:00:00 +0000</pubDate>
      <link>https://dev.to/jesstemporal/automating-css-versioning-in-staging-through-github-actions-obo</link>
      <guid>https://dev.to/jesstemporal/automating-css-versioning-in-staging-through-github-actions-obo</guid>
      <description>&lt;p&gt;While setting up a staging environment in a new hosting platform, I ran into an issue where static assets were aggressively cached with no straightforward way to invalidate them. This made staging deploys unreliable and validating changes slow.&lt;/p&gt;

&lt;p&gt;I could have spent hours fighting with cache headers and purge APIs, but there’s a simpler approach. Rather than fighting the cache, I leaned into patterns that avoid invalidation entirely and make staging behaviour explicit and predictable.&lt;/p&gt;

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

&lt;p&gt;This staging environment was behaving badly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static assets were cached aggressively&lt;/li&gt;
&lt;li&gt;Cache purging is unavailable&lt;/li&gt;
&lt;li&gt;Deploys appear as “successful” but changes were not visible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I was stuck with a cache that I couldn’t purge and changes I needed to validate somewhere other than my local machine. So I figured it was time to version the styling scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioned static assets
&lt;/h2&gt;

&lt;p&gt;One reliable way to bypass aggressive caching is to change the asset URL on every deploy.&lt;/p&gt;

&lt;p&gt;Static assets are referenced with a deploy-specific version, in my case I went for short git SHA.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/static/css/base.css?v=6ea4bbe"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was enough to fix the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CDNs cache by URL.&lt;/li&gt;
&lt;li&gt;A new URL guarantees a cache miss.&lt;/li&gt;
&lt;li&gt;No reliance on purge APIs or cache headers.&lt;/li&gt;
&lt;li&gt;Simple, deterministic behaviour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach works well for staging, where correctness matters more than caching efficiency. Then my next challenge was: &lt;em&gt;how to get this into my deployment without me manually editing the URLs every single time?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Label-gated staging deploys
&lt;/h2&gt;

&lt;p&gt;I can’t remember to update URLs manually every single time, so instead of suffering every time my CSS wouldn’t update accordingly, I adjusted my code and added a step in my GitHub Actions to take care of this for me.&lt;/p&gt;

&lt;p&gt;Since I’m the sole developer on this project, my staging deploys are explicitly controlled using pull request labels.&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%2Fumu4bpp40qzogltgc7ng.webp" 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%2Fumu4bpp40qzogltgc7ng.webp" alt="Pull request with preview label applied to enable staging deployment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A pull request is deployed to staging only when the &lt;code&gt;preview&lt;/code&gt; is applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;In case you want to replicate this for yourself, here’s how to do it. The steps are pretty simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run your tests;&lt;/li&gt;
&lt;li&gt;If tests pass deploy the app to the staging environment&lt;/li&gt;
&lt;li&gt;Make a comment on your PR so you know the version of the CSS that should be live&lt;/li&gt;
&lt;li&gt;Enjoy QA-ing your staged deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First the setup for your action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests and stage changes&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opened&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;synchronize&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reopened&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;labeled&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;unlabeled&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This names the action and tells it which Pull Requests to look at, in my case all of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tests running
&lt;/h3&gt;

&lt;p&gt;I want to make sure that all pull requests pass my test suite, so the first job checks out the pull request, installs the dependencies and creates necessary files, then runs the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout PR branch&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install uv&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;astral-sh/setup-uv@v7&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uv sync --extra dev&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup test environment&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cp .env.example .env&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uv run pytest&lt;/span&gt;

  &lt;span class="na"&gt;deploy-staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees code quality in all pull requests before I even consider deployment.&lt;/p&gt;

&lt;p&gt;With a successful test we can move on to deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Stage the deployment
&lt;/h3&gt;

&lt;p&gt;The deployment job only needs to run when the &lt;code&gt;preview&lt;/code&gt; label is included.&lt;/p&gt;

&lt;p&gt;Then a neat trick: you can get the SHA for the deployment with &lt;code&gt;github.sha&lt;/code&gt; and write it out to a file, in this case &lt;code&gt;.deploy_sha&lt;/code&gt; and once the code is sent to the cloud it can use that file to read the information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="na"&gt;deploy-staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Only run after tests pass&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains(github.event.pull_request.labels.*.name, 'preview')&lt;/span&gt;

    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout PR branch&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install uv&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;astral-sh/setup-uv@v7&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set deploy SHA&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "$" | cut -c1-7 &amp;gt; .deploy_sha&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to staging&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CLOUD_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$&lt;/span&gt;
          &lt;span class="na"&gt;CLOUD_APP_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# your deploy command here&lt;/span&gt;

      &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My code also needed to account for that. So first I created a function in my FastAPI app to grab the first few characters of the SHA from either the environment variable or the &lt;code&gt;.deploy_sha&lt;/code&gt; file. I also set a fall back to &lt;code&gt;dev&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Deploy SHA for cache busting - check env var first, then file, fallback to "dev"
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_deploy_sha&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get deploy SHA from environment or .deploy_sha file.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;sha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEPLOY_SHA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Try reading from file (created during CI/CD deploy)
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.deploy_sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Automatically make the deploy_sha available in all templates
&lt;/span&gt;&lt;span class="n"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy_sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_deploy_sha&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment variable is used in production which normally doesn’t change unless I see some weird caching I’m not expecting to, whereas while developing locally the fallback takes action and in staging we use the file.&lt;/p&gt;

&lt;p&gt;Finally the HTML template looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/static/css/base.css?v="&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we pass the &lt;code&gt;deploy_sha&lt;/code&gt; automatically as part of the globals variables for templates, any page will have the information they need when the application is being built.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Get a comment
&lt;/h3&gt;

&lt;p&gt;Finally, I wanted to get a comment I can see both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;That the deployment is live&lt;/li&gt;
&lt;li&gt;The hash I should look for in case I notice some discrepancies between what I’m seeing and the deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To this I added a final step to the &lt;code&gt;deploy-staging&lt;/code&gt; job with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;

  &lt;span class="nx"&gt;deploy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;staging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;

      &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Comment&lt;/span&gt; &lt;span class="nx"&gt;staging&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;PR&lt;/span&gt;
        &lt;span class="nx"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;v7&lt;/span&gt;
        &lt;span class="kd"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;!-- staging-deploy --&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n🚀 **Staging deploy complete** \n\nPreview: $\n\nCommit: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Find existing comment&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listComments&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;issue_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateComment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;comment_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createComment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;issue_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I also didn’t want every new commit to generate a new comment, I used an HTML comment to mark the message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;!-- staging-deploy --&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since comments render Markdown the HTML shows up in edit mode but gets hidden when displayed.&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%2F5fqlqbph4pm85ho5d51q.webp" 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%2F5fqlqbph4pm85ho5d51q.webp" alt="GitHub Actions comment showing staging deployment URL and commit SHA"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is what that looks like as the final step of the &lt;code&gt;deploy-staging&lt;/code&gt; is completed:&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%2F2z3hw80tefl3ov7ckegz.webp" 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%2F2z3hw80tefl3ov7ckegz.webp" alt="Staging deployment comment on pull request with deployment link and version SHA"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs of this approach
&lt;/h2&gt;

&lt;p&gt;This gave me full control over which pull request is staged and my staging represents “currently under review” making it easier for me to even QA the changes myself.&lt;/p&gt;

&lt;p&gt;The only downside from this approach is that I can’t have multiple ephemeral per-pull-request environments since I can only make one PR deployment at a time but that works well for my development workflow.&lt;/p&gt;

&lt;p&gt;The goal is clarity and control, not maximum automation.&lt;/p&gt;

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

&lt;p&gt;Versioned static assets and label-gated deploys solved my staging cache problem. Now CSS files get a git SHA in the URL, GitHub Actions handles deployments when I apply the &lt;code&gt;preview&lt;/code&gt; label, and I always know which version is live. No cache invalidation needed.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>git</category>
      <category>deployments</category>
      <category>css</category>
    </item>
    <item>
      <title>AI-powered software development flow: Lessons from shipping My Yarn Stash</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Tue, 03 Feb 2026 05:00:00 +0000</pubDate>
      <link>https://dev.to/jesstemporal/ai-powered-software-development-flow-lessons-from-shipping-my-yarn-stash-3bpb</link>
      <guid>https://dev.to/jesstemporal/ai-powered-software-development-flow-lessons-from-shipping-my-yarn-stash-3bpb</guid>
      <description>&lt;p&gt;When I started building &lt;a href="https://myyarnstash.app" rel="noopener noreferrer"&gt;&lt;strong&gt;My Yarn Stash&lt;/strong&gt;&lt;/a&gt;, I wasn’t trying to prove AI could build an app for me.&lt;/p&gt;

&lt;p&gt;I wanted to answer a practical question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What actually changes when you treat AI tools as long-term collaborators across planning, implementation, and design?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not with demos or toy examples, but with a real product that has users, persistent data, billing, authentication, and migrations—all the unglamorous constraints that come with shipping software meant to stick around.&lt;/p&gt;

&lt;p&gt;What follows is a breakdown of how AI fit into each phase, where it helped, where it failed, and what I learned or was reminded about tool boundaries by building something real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The day the AI deleted my database
&lt;/h2&gt;

&lt;p&gt;Early in the project, I asked a coding agent to help with a schema change. It confidently suggested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rm yarn.db
# Then recreate with new schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I let it.&lt;/p&gt;

&lt;p&gt;Gone. All the test data I’d been building up. Hours of manual entry to stress-test edge cases. Deleted in a single command because the AI treated the database like a throwaway artifact.&lt;/p&gt;

&lt;p&gt;The AI didn’t behave incorrectly. It did exactly what made sense if you assume databases are ephemeral. The problem was that &lt;strong&gt;I&lt;/strong&gt; hadn’t established guardrails.&lt;/p&gt;

&lt;p&gt;That moment reminded me to both pay more attention and adjust how I collaborated with AI tools. I needed to be very clear about constraints.&lt;/p&gt;

&lt;p&gt;Later I added rules to my copilot instructions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NEVER delete database files&lt;/li&gt;
&lt;li&gt;ALWAYS run migrations instead&lt;/li&gt;
&lt;li&gt;Before any destructive operation, run backups first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And went on to recreate my database. The AI didn’t change its behaviour. I changed mine.&lt;/p&gt;

&lt;p&gt;This became the pattern for the entire project: &lt;strong&gt;AI amplified my clarity and punished my ambiguity and lack of attention.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The database deletion reminded me of something fundamental: vague instructions produce unpredictable results. That lesson shaped how I approached every other phase of this project moving forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate conversations, separate contexts
&lt;/h2&gt;

&lt;p&gt;After the database incident, I knew I needed better constraint management. That started with how I structured conversations.&lt;/p&gt;

&lt;p&gt;I treated ChatGPT like a colleague I’d grab for specific discussions.&lt;/p&gt;

&lt;p&gt;Each feature got its own conversation thread:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One for billing and entitlements&lt;/li&gt;
&lt;li&gt;One for the AI extraction flow&lt;/li&gt;
&lt;li&gt;One for soft-delete patterns&lt;/li&gt;
&lt;li&gt;One for launch strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This wasn’t about organization. It was about &lt;strong&gt;context management&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2y50nst46jd3c5mrlg8l.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2y50nst46jd3c5mrlg8l.png" alt="schema showing separation of conversations in different chats" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When everything lives in one chat, the AI starts to get slow and confused, that’s when constant “summarizing history” increase time to answers, so as an engineer leveraging AI, you have to dutifully manage the context window. A billing conversation shouldn’t bleed into UI discussions. Implementation details shouldn’t pollute planning threads.&lt;/p&gt;

&lt;p&gt;Separate chats forced clarity. Each conversation had a single purpose, which made it easier to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stay focused on one problem&lt;/li&gt;
&lt;li&gt;Evaluate outputs against specific goals&lt;/li&gt;
&lt;li&gt;Make decisions without noise&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What those conversations actually looked like
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Branding: From vague to concrete&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I started with “I want it to feel cozy and calm.” Through discussion, we identified edge cases I hadn’t considered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text baked into images would need rework every time pricing changed&lt;/li&gt;
&lt;li&gt;Realistic imagery would read as generic stock photos&lt;/li&gt;
&lt;li&gt;“Organizing” visuals imply judgment. Better to frame as “inventory tracking”&lt;/li&gt;
&lt;li&gt;Gradients applied inconsistently would create visual noise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The conversation forced specificity. By the end, I had constraints like “no text in images” and “single vertical gradient system” that my coding agent could implement reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account page: Surfacing failure modes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When planning the account page, ChatGPT helped identify non-obvious problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timezone drift&lt;/strong&gt; : Automatically updating timezone when users travel would silently change project dates. Solution: only update on explicit user action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty state guilt&lt;/strong&gt; : Showing “0 finished projects” or prompting users to “finish your first project” creates pressure. Solution: gentle empty state with no call to action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These weren’t implementation details. They were experience risks that would erode trust if missed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Launch strategy: Reframing the goal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I initially thought about launch as a visibility event: where to post, how to announce.&lt;/p&gt;

&lt;p&gt;The discussion reframed it as a &lt;strong&gt;state change&lt;/strong&gt; : from “only I use this” to “other people can use this safely.”&lt;/p&gt;

&lt;p&gt;That shifted focus from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“How many people see this?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“What breaks when the first real users arrive?”&lt;/li&gt;
&lt;li&gt;“Is this experience internally consistent?”&lt;/li&gt;
&lt;li&gt;“Where can people get confused or succeed quickly?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I deprioritized announcements and spent more time on onboarding clarity instead.&lt;/p&gt;

&lt;p&gt;Every useful insight got distilled into markdown. Feature specs, constraints, architectural decisions: all rewritten in my own words with the help of ChatGPT, structured for clarity, stored alongside the code.&lt;/p&gt;

&lt;p&gt;This forced two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I had to actually understand the decision&lt;/li&gt;
&lt;li&gt;The output became durable and reviewable&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Stack decisions: What I chose vs. what I asked about
&lt;/h2&gt;

&lt;p&gt;With clear conversation boundaries established, I could make better stack decisions. I came into the project with strong opinions about some parts of the stack and no opinion at all about others.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqp58nhkrqpqjmbjqoeq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqp58nhkrqpqjmbjqoeq.png" alt="stack chosen" width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I decided before asking AI:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auth0&lt;/strong&gt; for authentication (familiar, reliable, scalable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; as the web framework (Python, async, good docs, also familiar and with great Auth0 support)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; for database and storage (Postgres with good tooling and prod database at 0 cost)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla JavaScript&lt;/strong&gt; and &lt;strong&gt;handwritten CSS&lt;/strong&gt; to start instead of a framework (less abstraction, easier to reason about with AI and for me to follow along)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resend for emailing users:&lt;/strong&gt; a welcome email for the users and another to notify myself when new user signed up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replicate:&lt;/strong&gt; to provide AI features like the label extraction information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These choices weren’t about AI compatibility. They were about reducing cognitive load and keeping a codebase I could jump in without the help of AI if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I asked AI to help evaluate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payment gateway&lt;/strong&gt; : I had no strong opinion. We discussed options and landed on &lt;strong&gt;Polar&lt;/strong&gt; for simplicity and developer experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment platform&lt;/strong&gt; : I wanted something low-cost that I already understood. Heroku fit both criteria.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern here matters: I brought constraints and context to the conversation instead of asking “what should I use?” in a vacuum.&lt;/p&gt;

&lt;p&gt;AI didn’t tell me what to build with. It helped me think through trade-offs for the decisions I hadn’t made yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning which model does what well
&lt;/h2&gt;

&lt;p&gt;Stack decisions settled, the next challenge was figuring out which AI tool to use when. Understanding model strengths came from repeated experience across different contexts.&lt;/p&gt;

&lt;p&gt;I didn’t use one AI tool for everything. That was deliberate. Through building, I found a flow that worked for me:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqtdb5pivooz9cl0fgj83.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqtdb5pivooz9cl0fgj83.png" alt="mermaid chart of a flow" width="800" height="1075"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT for planning and exploration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most planning conversations happened in ChatGPT in the mobile app. I used it on my phone during downtime to think through features, identify edge cases, and explore design decisions before writing any code.&lt;/p&gt;

&lt;p&gt;The mobile accessibility mattered. I could sketch out billing logic while having my coffee in the morning or map out future features during a commute. By the time I grabbed my computer to code, I’d already worked through the conceptual problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot Agents for straightforward work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well-defined low-complexity features and bugs went to GitHub issues and I would let Copilot handle implementation through PRs asynchronously by assigning Copilot to a given issue. It freed me up to work on more-complex and high value things while Copilot handled the implementation of the “no-brainer” tasks.&lt;/p&gt;

&lt;p&gt;When agents got stuck online, I’d download the code and ask Claude Opus to step in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code Copilot for local development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used VS Code with Copilot in different modes and with models depending on the work:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Claude Haiku:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mostly in Plan mode;&lt;/li&gt;
&lt;li&gt;Quick iterations when following up on features locally;&lt;/li&gt;
&lt;li&gt;Good for refactoring and adjusting implementation details that didn’t require more scope other than one file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Claude Opus:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mostly in Agent mode;&lt;/li&gt;
&lt;li&gt;Complex situations with many moving pieces&lt;/li&gt;
&lt;li&gt;Used when precision and accuracy mattered more than speed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No matter the model though, they were always constrained by explicit conventions in &lt;a href="http://copilot-instructions.md" rel="noopener noreferrer"&gt;&lt;code&gt;copilot-instructions.md&lt;/code&gt;&lt;/a&gt;. The lesson wasn’t “use the best model.” It was knowing not only &lt;strong&gt;when to switch&lt;/strong&gt; models and modes but also &lt;strong&gt;why switching&lt;/strong&gt; should happen.&lt;/p&gt;

&lt;p&gt;That judgment is a skill. AI tools don’t remove it.&lt;/p&gt;

&lt;p&gt;The database deletion happened because I let my guard down. I got comfortable with AI-assisted coding and stopped questioning the suggestions. It was a good reminder that different contexts need different constraints. By this phase of the project, I’d learned to match tool capabilities to task requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functional first, pretty later (because I’m not a designer)
&lt;/h2&gt;

&lt;p&gt;With implementation flowing more smoothly, I turned to design. This required a different kind of clarity.&lt;/p&gt;

&lt;p&gt;I deliberately delayed visual design work. Not because of anything AI-related, but because &lt;strong&gt;CSS and JavaScript are not my strong suit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I focused on making things functional first: flows, data models, interactions. Once those settled, I could tackle design from a more stable foundation. It didn’t look ugly but it clearly wasn’t beautiful:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/UulQpVyeEMs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;My first exploration with &lt;a href="https://stitch.withgoogle.com" rel="noopener noreferrer"&gt;Stitch&lt;/a&gt;, Google’s Gemini-based tool that transforms text descriptions into UI designs, showed me how powerful it was. I dropped the link of the working app and asked for a redesign. It made something pretty, but it didn’t look like &lt;em&gt;my&lt;/em&gt; product.&lt;/p&gt;

&lt;p&gt;So I used ChatGPT to put together the branding guidelines: tone, colour direction, what feelings I wanted the UI to convey. Once I had that document written down, I went back to Stitch with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screenshots of the functional app&lt;/li&gt;
&lt;li&gt;The branding document&lt;/li&gt;
&lt;li&gt;Specific constraints like “redesign this following the brand guidelines”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result you see below:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/VHyfaJH969o"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That combination worked. The design felt right because I’d done the thinking first. And did I mention it now supports dark mode as well?&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;Tools work better when you’ve already established intent.&lt;/strong&gt; Just like the database needed constraints, design tools needed direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this experience taught me about building with AI
&lt;/h2&gt;

&lt;p&gt;Going through all these steps I noticed a few patterns that go beyond individual tool choices. These are a few things I’m carrying forward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. AI doesn’t replace judgment. It makes judgment more important&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The database deletion wasn’t an AI failure. It was a clarity failure. Once I added explicit constraints, the tools became more reliable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Context boundaries matter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Separate conversations for separate concerns reduced drift and forced focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Model switching is a learned skill&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Understanding when to use a lightweight model versus a heavy one came from repeated friction, not documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Design still requires taste&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI can accelerate iteration, but you need to know what you’re aiming for. “Make it pretty” produces generic results. “Make it feel cozy and trustworthy” with examples gives you something to evaluate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Shipping something real teaches more than demos ever could&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most AI tool examples live in a world where you can always start over. Production software doesn’t. That constraint forced honesty about where AI helps and where it doesn’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for learning AI tools
&lt;/h2&gt;

&lt;p&gt;I always believed developers do better when they learn the limitations of the tools they use when building software. Specially since development is no-longer my day job, this project became an outlet for experiment and trying new tools and it helped shape how I evaluate and talk about AI tools.&lt;/p&gt;

&lt;p&gt;Demos have their place in my world, but nothing beats the actual experience of building something and learn by doing.&lt;/p&gt;

&lt;p&gt;Building My Yarn Stash gave me a even clearer sense of where AI accelerates work and where it introduces risk. Not from reading about it, but from hitting the boundaries myself.&lt;/p&gt;

&lt;p&gt;If you’re trying to find your footing with AI tools: &lt;strong&gt;build something real.&lt;/strong&gt; Solve a problem you face often, try the new tools and find the flow that works for you.&lt;/p&gt;

&lt;p&gt;Step away for a moment from the tutorials and courses and build your own project free of the constraints instructors purposely impose in order to teach and give students a good experience. Something with users, data you can’t lose, and decisions that compound over time.&lt;/p&gt;

&lt;p&gt;You’ll learn more from one broken migration than from a hundred perfect prompts.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>chatgpt</category>
      <category>githubcopilot</category>
    </item>
  </channel>
</rss>
