<?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: Reeve </title>
    <description>The latest articles on DEV Community by Reeve  (@fablefusestudios).</description>
    <link>https://dev.to/fablefusestudios</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%2F4172422%2F37afd1a8-ecaf-4626-8011-d72c00117f2e.png</url>
      <title>DEV Community: Reeve </title>
      <link>https://dev.to/fablefusestudios</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fablefusestudios"/>
    <language>en</language>
    <item>
      <title>Five ways my Gemini-for-Claude-Code setup broke, and the fixes</title>
      <dc:creator>Reeve </dc:creator>
      <pubDate>Mon, 12 Oct 2026 00:21:50 +0000</pubDate>
      <link>https://dev.to/fablefusestudios/five-ways-my-gemini-for-claude-code-setup-broke-and-the-fixes-h7n</link>
      <guid>https://dev.to/fablefusestudios/five-ways-my-gemini-for-claude-code-setup-broke-and-the-fixes-h7n</guid>
      <description>&lt;p&gt;I set up Claude Code to hand research and review jobs to Gemini. Over two days of building I hit a retired model, overloaded endpoints, an exhausted quota, an empty reply from a thinking model, and an agent that wanted to print my API key. None of these is exotic. Anyone wiring a second model into a coding agent will meet them, so here's each failure, how to recognise it from the API response, and what fixed it.&lt;/p&gt;

&lt;p&gt;The snippets are trimmed from the broker I ended up writing (Python, &lt;code&gt;httpx&lt;/code&gt;, Gemini REST &lt;code&gt;generateContent&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The model ID was retired
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; a model ID that worked in every tutorial returned HTTP 404, saying the model is no longer available to new users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; model IDs get retired, and "retired for new users" means a fresh API key can't call a model that older keys still can. A hard-coded ID from a blog post (or from your own memory) is a time bomb.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; ask the API which models your key can actually call, and treat 404 as "fail fast with the API's own message", never "retry".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&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;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quota&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&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;quota&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;RETRYABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# {429, 500, 502, 503, 504}
&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;overloaded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;404&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;not_found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;           &lt;span class="c1"&gt;# retired or misspelt model: don't retry
&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;bad_request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. "High demand" 503s
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; 503s on two Flash models at the same time, with the agent sitting there for minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; jittered exponential backoff that honours &lt;code&gt;Retry-After&lt;/code&gt;, capped, then a fallback chain of models, with a small retry budget per model so a saturated chain fails in seconds rather than minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_delay_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_delay_s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry-after&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;r&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_delay_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ra&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;delay&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&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="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. A quota error isn't a transient error
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;429 RESOURCE_EXHAUSTED&lt;/code&gt;. My first retry loop lumped it in with 503s, so it kept retrying, and every retry spent more of a daily allowance that was already gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; a 429 for "too many requests right now" and a 429 for "you've used today's quota" look the same at the status-code level. The message tells them apart. On the free tier I measured roughly 20 requests per day per model, so this happens fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; classify quota separately (see &lt;code&gt;_classify&lt;/code&gt; above) and trip a process-wide circuit breaker. Every later call fails immediately, without touching the API, until a cooldown passes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuotaBreaker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Once the project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s quota is exhausted, stop calling the API for a while.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cooldown_s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooldown_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tripped_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cooldown_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tripped_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tripped_at&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tripped_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooldown_s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. The thinking model returned an empty string
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; HTTP 200, no error, and &lt;code&gt;text == ""&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; thinking tokens come out of the same output budget as the answer. In my logs, a one-word reply cost 2 answer tokens and 68 thinking tokens. Set a small &lt;code&gt;maxOutputTokens&lt;/code&gt; and the thinking can use all of it, leaving nothing for the answer. The response tells you, but only in &lt;code&gt;finishReason: MAX_TOKENS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; give thinking models real output headroom, drop the thought parts, and turn an empty answer into an error that names the cause instead of returning a blank string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thought&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;hint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; - raise max_output_tokens (thinking used the budget)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finishReason&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAX_TOKENS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;empty response (finishReason=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;finishReason&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. The agent tried to "check" my API key by printing it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; to confirm the key was set, Claude Code proposed &lt;code&gt;echo $GEMINI_API_KEY&lt;/code&gt;. That puts the key into the transcript, the logs and anything you later share.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; a &lt;code&gt;PreToolUse&lt;/code&gt; hook that blocks the common leak paths (echoing or printing &lt;code&gt;*_KEY&lt;/code&gt;/&lt;code&gt;*_TOKEN&lt;/code&gt; variables, &lt;code&gt;printenv&lt;/code&gt;/&lt;code&gt;env&lt;/code&gt; dumps, PowerShell &lt;code&gt;$env:&lt;/code&gt;, reading &lt;code&gt;.env&lt;/code&gt; files, writing live-looking keys into code) and suggests a presence check instead (&lt;code&gt;[ -n "$VAR" ] &amp;amp;&amp;amp; echo set&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;One gotcha: on Windows some shells pipe the hook's JSON input with a UTF-8 BOM. A plain &lt;code&gt;json.loads&lt;/code&gt; then raises, and a hook that fails open on bad input lets everything through. Decode with &lt;code&gt;utf-8-sig&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="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&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;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8-sig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pattern-based, so it's a seatbelt, not a sandbox. It catches the common ways an agent leaks a key, not every possible one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;Claude stays the primary agent. It delegates research and second-opinion reviews through the broker, then checks what comes back before acting on it. The broker owns retries, fallback, the quota breaker and usage logging; the hook sits between the agent and your environment variables.&lt;/p&gt;

&lt;p&gt;The hook is free and MIT-licensed on its own: &lt;a href="https://github.com/fable-and-fuse/claude-secret-guard" rel="noopener noreferrer"&gt;claude-secret-guard&lt;/a&gt;. The full kit (broker, a Claude Code subagent, &lt;code&gt;/research&lt;/code&gt; and &lt;code&gt;/second-opinion&lt;/code&gt; commands, the hook and 57 offline tests) is &lt;a href="https://fable-and-fuse.github.io/brainrot-colour-chaos-site/switchboard/" rel="noopener noreferrer"&gt;Switchboard&lt;/a&gt;, a one-time US$29 download. You bring your own Gemini API key, so Google's quotas and pricing apply.&lt;/p&gt;

&lt;p&gt;What failure modes have you hit when wiring two models together? I'd especially like to hear about ones that look like success, like the empty-string case.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was drafted with help from an AI assistant (Claude). The errors, numbers and code come from Switchboard's own source and build logs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>claude</category>
      <category>gemini</category>
    </item>
    <item>
      <title>What Building Switchboard Taught Me About Multi-Model AI Workflows</title>
      <dc:creator>Reeve </dc:creator>
      <pubDate>Fri, 09 Oct 2026 04:52:12 +0000</pubDate>
      <link>https://dev.to/fablefusestudios/what-building-switchboard-taught-me-about-multi-model-ai-workflows-3p5h</link>
      <guid>https://dev.to/fablefusestudios/what-building-switchboard-taught-me-about-multi-model-ai-workflows-3p5h</guid>
      <description>&lt;p&gt;I'm Reeve, the founder of &lt;strong&gt;Fable &amp;amp; Fuse Studios&lt;/strong&gt;, and lately I've been experimenting with something I've become increasingly interested in: building practical developer workflows that use more than one AI model.&lt;/p&gt;

&lt;p&gt;That work led to &lt;strong&gt;Switchboard v1.0.1&lt;/strong&gt;, a multi-model developer toolkit designed for Claude Code.&lt;/p&gt;

&lt;p&gt;I wanted to share the thinking behind the project, some of the engineering challenges, and what I've learned so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why build a multi-model workflow?
&lt;/h2&gt;

&lt;p&gt;One thing I've learned from experimenting with agentic AI is that different models and tools have different strengths.&lt;/p&gt;

&lt;p&gt;A powerful reasoning model may be useful for planning and complex programming. A smaller local model may be sufficient for narrow classification or extraction tasks. And sometimes a simple Python script is more predictable than either.&lt;/p&gt;

&lt;p&gt;The interesting problem isn't just connecting those components.&lt;/p&gt;

&lt;p&gt;It's deciding &lt;strong&gt;which component should handle which job, what happens when it fails, and how to keep the workflow understandable&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three lessons from building Switchboard
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A fallback needs to understand why something failed
&lt;/h3&gt;

&lt;p&gt;A temporary service failure and an exhausted API quota are not the same problem.&lt;/p&gt;

&lt;p&gt;Retrying a request may make sense when a service is temporarily unavailable. But when a provider has exhausted its available quota, repeatedly trying the same request doesn't solve anything.&lt;/p&gt;

&lt;p&gt;I wanted the toolkit's provider-handling workflow to distinguish these situations rather than blindly retrying everything.&lt;/p&gt;

&lt;p&gt;The wider lesson: a fallback system is only useful when it responds appropriately to the actual failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Security hooks are useful, but they aren't magic
&lt;/h3&gt;

&lt;p&gt;When AI coding tools can inspect files and run commands, accidentally exposing credentials becomes a genuine concern.&lt;/p&gt;

&lt;p&gt;Switchboard includes security-minded tooling, and I've also been working with a separate free Claude Code secret-guard project.&lt;/p&gt;

&lt;p&gt;But there's an important distinction: a protective hook can help catch certain risky operations without guaranteeing that every possible path to credential exposure is blocked.&lt;/p&gt;

&lt;p&gt;Good security still depends on careful credential storage, restricted permissions, sensible tool access and understanding where the safeguards end.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. More AI agents don't automatically mean better engineering
&lt;/h3&gt;

&lt;p&gt;It can be tempting to keep adding models, agents and integrations because the architecture looks increasingly sophisticated.&lt;/p&gt;

&lt;p&gt;But complexity has a cost.&lt;/p&gt;

&lt;p&gt;A useful workflow should make it clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What each component is responsible for&lt;/li&gt;
&lt;li&gt;When a model call is actually necessary&lt;/li&gt;
&lt;li&gt;How failures are handled&lt;/li&gt;
&lt;li&gt;How results are checked&lt;/li&gt;
&lt;li&gt;Which operations require human approval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm increasingly interested in making these systems more dependable rather than simply making them larger.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Switchboard currently focuses on
&lt;/h2&gt;

&lt;p&gt;The v1.0.1 toolkit brings together utilities for experimenting with multi-model Claude Code workflows, including provider integration, quota-aware handling, research and second-opinion commands, and secret-protection tooling.&lt;/p&gt;

&lt;p&gt;It's a developer toolkit, not a replacement for understanding your own code or security responsibilities. External AI provider availability, quotas and API costs still matter.&lt;/p&gt;

&lt;p&gt;I'm continuing to learn which parts of this approach are genuinely useful outside my own development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm interested in learning from other developers
&lt;/h2&gt;

&lt;p&gt;I'd be interested to hear how other people handle these challenges.&lt;/p&gt;

&lt;p&gt;Do you use one primary coding model, or several? Have you built fallback mechanisms for provider failures? And do you prefer model-driven orchestration or smaller deterministic scripts for routine tasks?&lt;/p&gt;

&lt;p&gt;For anyone interested in the project itself, here's the &lt;a href="https://fable-and-fuse.github.io/brainrot-colour-chaos-site/switchboard/" rel="noopener noreferrer"&gt;Switchboard project page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm looking forward to sharing more practical lessons as I continue building.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>devtools</category>
      <category>security</category>
    </item>
  </channel>
</rss>
