<?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: eraydiler</title>
    <description>The latest articles on DEV Community by eraydiler (@eraydiler).</description>
    <link>https://dev.to/eraydiler</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%2F1046478%2F7715e30e-e0d5-4315-9a6b-eeb256f8b39d.png</url>
      <title>DEV Community: eraydiler</title>
      <link>https://dev.to/eraydiler</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eraydiler"/>
    <language>en</language>
    <item>
      <title>Automated PR Reviews with Cline and NVIDIA NIM APIs</title>
      <dc:creator>eraydiler</dc:creator>
      <pubDate>Sat, 26 Sep 2026 14:44:41 +0000</pubDate>
      <link>https://dev.to/eraydiler/automated-pr-reviews-with-cline-and-nvidia-nim-apis-1iga</link>
      <guid>https://dev.to/eraydiler/automated-pr-reviews-with-cline-and-nvidia-nim-apis-1iga</guid>
      <description>&lt;p&gt;AI has been evolving at a breakneck pace ever since the first consumer-facing products landed. I've been updating my personal development workflow since day one, keeping my tools and habits aligned with each new wave of progress.&lt;/p&gt;

&lt;p&gt;Over time, I've worked with a long list of models and tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ChatGPT&lt;/li&gt;
&lt;li&gt;Claude&lt;/li&gt;
&lt;li&gt;Gemini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursor&lt;/li&gt;
&lt;li&gt;GitHub Copilot for VS Code&lt;/li&gt;
&lt;li&gt;GitHub Copilot for Xcode&lt;/li&gt;
&lt;li&gt;Xcode Coding Intelligence&lt;/li&gt;
&lt;li&gt;Trae&lt;/li&gt;
&lt;li&gt;VS Code plugins: Cline, Roo Code, …&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and the list keeps growing.&lt;/p&gt;

&lt;p&gt;For the last year or so I've been developing with &lt;strong&gt;TRAE / Gemini&lt;/strong&gt; on macOS, and both the IDE and the model served me well. But those tools and models come with two recurring pain points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Third-party apps&lt;/strong&gt; mean your data sits in someone else's database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription fees&lt;/strong&gt; can add up fast.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The natural answer to both is &lt;strong&gt;local LLMs&lt;/strong&gt;. I tested that route on my &lt;strong&gt;MacBook M1 Pro&lt;/strong&gt; with &lt;strong&gt;LM Studio&lt;/strong&gt;, pulling models locally and wiring them into VS Code / Xcode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemma&lt;/li&gt;
&lt;li&gt;Qwen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and a few others.&lt;/p&gt;

&lt;p&gt;The honest result: on my hardware, local models weren't strong enough to beat the cloud subscription experience.&lt;/p&gt;

&lt;p&gt;Then, a few days ago, I discovered that &lt;strong&gt;NVIDIA&lt;/strong&gt; is running open models on its own servers for free at &lt;a href="https://build.nvidia.com" rel="noopener noreferrer"&gt;build.nvidia.com&lt;/a&gt;. I immediately wanted to try my workflow against that service — first with the &lt;strong&gt;NVIDIA NIM Provider&lt;/strong&gt; extension in VS Code, then side-by-side with &lt;strong&gt;Cline&lt;/strong&gt;. The &lt;strong&gt;Cline + NVIDIA NIM&lt;/strong&gt; combo turned out to be noticeably more stable, so that's what stuck.&lt;/p&gt;

&lt;p&gt;I was about to undergo a major refactor on one of my projects &lt;strong&gt;TaxCalculator&lt;/strong&gt;: turning the Flask/Python monolith (thousands of lines in &lt;code&gt;index.html&lt;/code&gt; plus a tangled frontend) into a sliced, testable, readable, and maintainable structure.&lt;/p&gt;



&lt;p&gt;That result genuinely made my day.&lt;/p&gt;

&lt;p&gt;Just before merging the refactor PR, one more question popped into my head: &lt;strong&gt;Cline + NVIDIA NIM&lt;/strong&gt; had carried me this far — could the same duo review my PRs automatically through &lt;strong&gt;GitHub Actions&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Spoiler: yes, it can. Here's the full story, including the bumps along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Automated PR Review?
&lt;/h2&gt;

&lt;p&gt;I'm already constantly reviewing AI-generated code during development and again at PR time. Adding a third pass — an automated PR review bot — is the obvious next step for both &lt;strong&gt;security&lt;/strong&gt; and &lt;strong&gt;robustness&lt;/strong&gt;, right? An LLM-based agent is especially good at catching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain-specific rules&lt;/strong&gt; (Clean Architecture, &lt;code&gt;Decimal&lt;/code&gt; for money, no hardcoded secrets)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases / null-safety&lt;/strong&gt; issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test coverage&lt;/strong&gt; gaps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API &amp;amp; architecture&lt;/strong&gt; inconsistencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cline CLI is ideal for this: it runs headless, integrates with &lt;code&gt;gh&lt;/code&gt;, and supports any OpenAI-compatible API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR opened → GitHub Actions workflow triggers
          → Diff fetch (1 API call, capped by context budget)
          → Cline CLI: read review rules + analyze diff
          → gh pr review --comment to leave inline feedback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three files do the work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Universal review rules&lt;/strong&gt; (same across every project: language, secrets, dependencies, naming, commits)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.clinerules/taxcalculator.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Project-specific rules&lt;/strong&gt; (Clean Arch boundaries, &lt;code&gt;Decimal&lt;/code&gt;, zero-data-retention, mandatory Flask blueprints, &lt;code&gt;LoggerService&lt;/code&gt;, &lt;code&gt;ConcurrencyService&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.github/workflows/cline-pr-review.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CI workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why two rule files?&lt;/strong&gt; So I can copy &lt;code&gt;AGENTS.md&lt;/code&gt; verbatim into other projects. Cline auto-loads anything inside &lt;code&gt;.clinerules/&lt;/code&gt; natively — no extra config needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Add the secret
&lt;/h3&gt;

&lt;p&gt;In the GitHub repo, go to &lt;strong&gt;Settings → Secrets and variables → Actions&lt;/strong&gt; and add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NVIDIA_API_KEY&lt;/code&gt; = your key from build.nvidia.com&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Write the workflow file
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ready_for_review&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cline-review&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;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;24'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm install -g cline&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;Authenticate Cline with NVIDIA NIM&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;NVIDIA_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;secrets.NVIDIA_API_KEY&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cline auth --provider openai \&lt;/span&gt;
            &lt;span class="s"&gt;--apikey "$NVIDIA_API_KEY" \&lt;/span&gt;
            &lt;span class="s"&gt;--baseurl "https://integrate.api.nvidia.com/v1" \&lt;/span&gt;
            &lt;span class="s"&gt;--modelid "moonshotai/kimi-k3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The 6 Problems I Hit (and how I fixed them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Node 20 deprecation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; &lt;code&gt;Node.js 20 is deprecated... being forced to run on Node.js 24&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; &lt;code&gt;node-version: '24'&lt;/code&gt; (Cline CLI also requires Node 22.15+ for TLS trust store).&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;code&gt;unknown option '--base-url'&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; cline CLI does not accept dashed flags for this option.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Drop the dash: &lt;code&gt;--baseurl "https://integrate.api.nvidia.com/v1"&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. &lt;code&gt;auth quick setup requires --modelid&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; quick-setup mode forces you to pick a model.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Add &lt;code&gt;--modelid "moonshotai/kimi-k3"&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. &lt;code&gt;429 Too Many Requests&lt;/code&gt; (rate limit)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; NVIDIA's free tier caps you at &lt;strong&gt;40 requests per minute&lt;/strong&gt;. Every time Cline reads a slice of the diff, that's one request to the LLM. A large diff made Cline fetch it in several chunks, passing the 40 limit in under a minute — as a result, the API responded with HTTP 429 and stopped serving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix — two layers:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(a) Diff pre-preparation&lt;/strong&gt; in the workflow, not in Cline:&lt;br&gt;
Fetch the full diff once, cap it by a budget derived from the model's context window, and derive the file list locally. Cline then just &lt;code&gt;cat&lt;/code&gt;s the prepared files — one read, one request.&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="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;Prepare PR diff&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;MODEL_CONTEXT_WINDOW&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128000"&lt;/span&gt;   &lt;span class="c1"&gt;# token limit of the model&lt;/span&gt;
    &lt;span class="na"&gt;DIFF_BUDGET_RATIO&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.7"&lt;/span&gt;         &lt;span class="c1"&gt;# share of context for the diff&lt;/span&gt;
    &lt;span class="na"&gt;DIFF_TOKENS_PER_LINE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;        &lt;span class="c1"&gt;# avg token density of a unified diff&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;# Fetch the diff once. Each subsequent read by Cline would be another&lt;/span&gt;
    &lt;span class="s"&gt;# LLM call, burning our 40 RPM budget.&lt;/span&gt;
    &lt;span class="s"&gt;gh pr diff "$PR" &amp;gt; /tmp/diff_full.txt&lt;/span&gt;
    &lt;span class="s"&gt;TOTAL_LINES=$(wc -l &amp;lt; /tmp/diff_full.txt)&lt;/span&gt;

    &lt;span class="s"&gt;# Derive the line cap from the model's context window. This is NOT a&lt;/span&gt;
    &lt;span class="s"&gt;# magic number — change MODEL_CONTEXT_WINDOW to retarget a different model.&lt;/span&gt;
    &lt;span class="s"&gt;MAX_LINES=$(awk -v ctx="$MODEL_CONTEXT_WINDOW" -v ratio="$DIFF_BUDGET_RATIO" \&lt;/span&gt;
                   &lt;span class="s"&gt;-v tpl="$DIFF_TOKENS_PER_LINE" \&lt;/span&gt;
                   &lt;span class="s"&gt;'BEGIN { printf "%d", ctx * ratio / tpl }')&lt;/span&gt;

    &lt;span class="s"&gt;# Truncate to the cap. Anything beyond is dropped with GitHub Actions&lt;/span&gt;
    &lt;span class="s"&gt;# warning annotation so reviewers see why.&lt;/span&gt;
    &lt;span class="s"&gt;if [ "$TOTAL_LINES" -le "$MAX_LINES" ]; then&lt;/span&gt;
      &lt;span class="s"&gt;cp /tmp/diff_full.txt /tmp/diff.txt&lt;/span&gt;
    &lt;span class="s"&gt;else&lt;/span&gt;
      &lt;span class="s"&gt;head -"$MAX_LINES" /tmp/diff_full.txt &amp;gt; /tmp/diff.txt&lt;/span&gt;
      &lt;span class="s"&gt;echo "::warning::Diff truncated from $TOTAL_LINES to $MAX_LINES lines"&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;

    &lt;span class="s"&gt;# Build the file list locally from 'diff --git' headers — no extra API call.&lt;/span&gt;
    &lt;span class="s"&gt;grep '^diff --git' /tmp/diff_full.txt | sed -E 's|^diff --git a/(.+) b/.*|\1|' \&lt;/span&gt;
      &lt;span class="s"&gt;&amp;gt; /tmp/diff_summary.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;(b) Strict prompt budget&lt;/strong&gt; — max 5 tool calls, &lt;code&gt;gh pr diff&lt;/code&gt; banned:&lt;br&gt;
Without these rules, an LLM agent will happily re-fetch the diff, read extra files for "context", and burn through your rate budget. We tell Cline exactly what we want it to do, in what order, and how many steps it's allowed to take.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Do NOT call 'gh pr diff' again.
- Make at most 5 tool calls total.
- Read /tmp/diff_summary.txt and /tmp/diff.txt via 'cat'.
- Post all review comments in ONE 'gh pr review --comment' call.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;code&gt;unknown flag: --stat&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; &lt;code&gt;gh pr diff --stat&lt;/code&gt; does not exist in this &lt;code&gt;gh&lt;/code&gt; version (only &lt;code&gt;--name-only&lt;/code&gt; and &lt;code&gt;--patch&lt;/code&gt;).&lt;br&gt;
&lt;strong&gt;What we wanted it for:&lt;/strong&gt; the "X files changed, +Y −Z" summary you see on the PR page. We needed that list so Cline could see the affected files before reading the diff.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Extract the file list locally from the &lt;code&gt;diff --git a/... b/...&lt;/code&gt; headers in the diff itself. No extra API call, just text processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'^diff --git'&lt;/span&gt; /tmp/diff_full.txt | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'s|^diff --git a/(.+) b/.*|\1|'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Retries duplicate findings
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; On a transient 429 or socket drop, naively adding &lt;code&gt;|| sleep 60 &amp;amp;&amp;amp; retry&lt;/code&gt; seems safe. In practice, it isn't — Cline is invoked as a fresh CLI process each time, so the retry restarts from scratch: re-reads the diff, re-reads context, and posts &lt;strong&gt;duplicate review comments&lt;/strong&gt; on the same PR.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; No retry. Run Cline once, fail loudly on failure. If a future change makes retries necessary, persist Cline's intermediate state to disk between attempts so a retry can resume.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# No retry — Cline must complete in one session.&lt;/span&gt;
cline &lt;span class="nt"&gt;--auto-approve&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROMPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'::error::Cline review failed'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How We Pick the Dynamic Line Limit
&lt;/h2&gt;

&lt;p&gt;The cap is &lt;strong&gt;not a magic number&lt;/strong&gt;. It's derived from three constants that the workflow exposes as environment variables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MODEL_CONTEXT_WINDOW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;128000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Token limit of the model you call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DIFF_BUDGET_RATIO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Share of the context reserved for the diff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DIFF_TOKENS_PER_LINE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Average token density of a unified diff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The formula:&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;max_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context_window&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;diff_budget_ratio&lt;/span&gt; &lt;span class="err"&gt;÷&lt;/span&gt; &lt;span class="n"&gt;diff_tokens_per_line&lt;/span&gt;
         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128_000&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="err"&gt;÷&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
         &lt;span class="err"&gt;≈&lt;/span&gt; &lt;span class="mi"&gt;29_866&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a typical PR with fewer than ~30k diff lines is reviewed in full. Anything larger is truncated, and the workflow emits a &lt;code&gt;::warning::&lt;/code&gt; annotation explaining the cap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not "unlimited"?&lt;/strong&gt; Three real upper bounds apply, and any of them can break the run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Model context window&lt;/strong&gt; — the diff, system prompt, rules, and the agent's output all share one token budget. Overflow = truncated reasoning or broken output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow timeout&lt;/strong&gt; — the job-level &lt;code&gt;timeout-minutes&lt;/code&gt; caps the total runtime. A multi-hundred-thousand-line diff cannot finish reviewing inside the window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limit&lt;/strong&gt; — free NVIDIA NIM caps at 40 RPM. A giant diff turns into many LLM calls if the agent reads it in pieces.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs you can tune:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DIFF_BUDGET_RATIO = 0.9&lt;/code&gt; → bigger diff, less room for Cline's reasoning/output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DIFF_BUDGET_RATIO = 0.5&lt;/code&gt; → smaller diff, larger safety margin.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timeout-minutes&lt;/code&gt; → increase for very large diffs, decrease for faster feedback.&lt;/li&gt;
&lt;li&gt;Switch models → set &lt;code&gt;MODEL_CONTEXT_WINDOW&lt;/code&gt; (e.g. &lt;code&gt;32000&lt;/code&gt;) and the cap recomputes automatically.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Result: A Real PR Review
&lt;/h2&gt;

&lt;p&gt;On a real PR, the workflow produced output like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚨 &lt;strong&gt;Critical&lt;/strong&gt;: duplicate block in &lt;code&gt;report_service._create_trades_from_lists&lt;/code&gt;, hard-coded strings, &lt;code&gt;remember=True&lt;/code&gt; cookie leaking across sessions&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Important&lt;/strong&gt;: privacy/data-retention violation (&lt;code&gt;cvs_hash&lt;/code&gt; persisted server-side), missing integration tests under &lt;code&gt;tests/unit/parsers/&lt;/code&gt;, architecture inconsistencies&lt;/li&gt;
&lt;li&gt;✨ &lt;strong&gt;Nits&lt;/strong&gt;: quote style inconsistency, &lt;code&gt;NonJSON&lt;/code&gt; typo, multi-line logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: &lt;strong&gt;universal rules + project-specific rules + single-shot review + dynamic diff budget&lt;/strong&gt; combined into a real, value-adding automated review running on a free tier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Example: TaxCalculator Refactor (PR #13)
&lt;/h2&gt;

&lt;p&gt;Here's an actual PR review generated by the workflow on my &lt;strong&gt;TaxCalculator&lt;/strong&gt; project — a large refactor turning a Flask monolith into Clean Architecture boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Review — eraydiler/TaxCalculator#13
&lt;/h3&gt;

&lt;p&gt;Overall, the PR is a large refactor that improves structure (Clean Architecture boundaries, partials, JS modules, tests). However, several &lt;strong&gt;blocking issues&lt;/strong&gt; need attention before merging.&lt;/p&gt;

&lt;h4&gt;
  
  
  🔴 Blocking — Privacy: zero-data-retention claim is violated
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;ARCHITECTURE.md&lt;/code&gt; (and &lt;code&gt;README.md&lt;/code&gt;) explicitly promise &lt;em&gt;"No user report data (CSV content, tax summary) is persisted on the server. All report data lives exclusively in the client's IndexedDB."&lt;/em&gt; The diff contradicts this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/models/report_task.py&lt;/code&gt; stores &lt;code&gt;csv_content&lt;/code&gt; on the model:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csv_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task_data&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;csv_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="bp"&gt;...&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csv_content&lt;/span&gt;&lt;span class="sh"&gt;"&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;csv_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/repositories/mongo_task_repository.py&lt;/code&gt; writes &lt;code&gt;csv_content&lt;/code&gt; to MongoDB:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;csv_content&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="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;update_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csv_content&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="n"&gt;csv_content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/blueprints/auth.py&lt;/code&gt; (&lt;code&gt;download_csv&lt;/code&gt;) reads &lt;code&gt;task.csv_content&lt;/code&gt; from MongoDB and serves it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project-specific rule &lt;strong&gt;Privacy: Server must NOT persist CSV report content or tax summaries&lt;/strong&gt; is being broken by this change. Either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove &lt;code&gt;csv_content&lt;/code&gt; from &lt;code&gt;ReportTask&lt;/code&gt; and persist &lt;em&gt;only&lt;/em&gt; &lt;code&gt;csv_hash&lt;/code&gt; (the documented design), and make the client responsible for retaining the CSV in IndexedDB for re-download — &lt;strong&gt;or&lt;/strong&gt; —&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; / &lt;code&gt;README.md&lt;/code&gt; to reflect the actual policy, and get explicit sign-off, since this is a documented user-facing privacy guarantee.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  🟡 Logic / Security
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/blueprints/auth.py&lt;/code&gt;: the upload endpoint computes &lt;code&gt;csv_hash&lt;/code&gt; for integrity but never compares the stored hash against a client-supplied hash on download — the integrity check described in &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; is one-directional only. If you keep &lt;code&gt;csv_content&lt;/code&gt; in Mongo, at least verify hash equality before serving.&lt;/li&gt;
&lt;li&gt;The webhook signature path uses &lt;code&gt;X-Signature&lt;/code&gt; header; confirm this matches Lemon Squeezy's actual header name (&lt;code&gt;X-Signature&lt;/code&gt; is correct for Lemon Squeezy — OK, but worth a comment).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/unit/test_webhook_idempotency.py&lt;/code&gt; patches &lt;code&gt;src.app.payment_service.user_repository&lt;/code&gt; but the production code accesses the repo via the service, not the module-level attribute. Verify the patch target; otherwise this test passes without exercising the real path.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  🟡 Tests
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;New tests added (good), but no unit test exists for the refactored &lt;code&gt;mongo_task_repository&lt;/code&gt; &lt;code&gt;create_task_metadata&lt;/code&gt; / &lt;code&gt;update_task_status&lt;/code&gt; paths. Add coverage under &lt;code&gt;tests/unit/&lt;/code&gt; for both the success path and the privacy rule (assert that no &lt;code&gt;csv_content&lt;/code&gt; is written when the zero-retention policy is enforced).&lt;/li&gt;
&lt;li&gt;The download-race-condition test should also assert that &lt;code&gt;csv_content&lt;/code&gt; is &lt;strong&gt;never&lt;/strong&gt; written by the server, to lock in the privacy guarantee.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  🟢 Minor / Style
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tests/integration/test_end_to_end.py&lt;/code&gt; still contains &lt;code&gt;print(...)&lt;/code&gt; debug noise (&lt;code&gt;print("\nRow differences found:")&lt;/code&gt; etc.). Replace with &lt;code&gt;LoggerService&lt;/code&gt; per the logging rule.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/blueprints/auth.py&lt;/code&gt; returns &lt;code&gt;{"error": "Geçersiz dosya formatı..."}&lt;/code&gt; — user-facing strings must be English unless localization is added; this is Turkish. Same applies to any other tr-string errors in the diff.&lt;/li&gt;
&lt;li&gt;Commit messages are clean and descriptive — good.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ✅ Looks good
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Decimal&lt;/code&gt; is used consistently for monetary values (no &lt;code&gt;float&lt;/code&gt; introduced for tax/money).&lt;/li&gt;
&lt;li&gt;Blueprints live under &lt;code&gt;src/blueprints/&lt;/code&gt;, not inline in &lt;code&gt;app.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConcurrencyService&lt;/code&gt; is used for parallel work.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LoggerService&lt;/code&gt; is used for errors/info.&lt;/li&gt;
&lt;li&gt;JS is split into focused ES modules with proper single-responsibility boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please address the &lt;strong&gt;Blocking&lt;/strong&gt; privacy item first — it directly violates a documented user-facing promise and a project-specific review rule.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LLM agents are not cheap&lt;/strong&gt; — even 40 RPM is tight, careful budgeting is essential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI flags drift across versions&lt;/strong&gt; — always check &lt;code&gt;--help&lt;/code&gt; before writing the workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch in the workflow, not in the agent&lt;/strong&gt; — pre-preparation saves API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't retry blindly&lt;/strong&gt; — a fresh Cline process restarts from scratch and duplicates findings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep universal rules separate from project-specific rules&lt;/strong&gt; — reusable vs portable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derive magic numbers from real constraints&lt;/strong&gt; — context window, timeout, rate limit — instead of guessing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real PR reviews catch real problems&lt;/strong&gt; — the privacy violation in TaxCalculator#13 would have been missed by manual review alone.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>automation</category>
      <category>prreviews</category>
      <category>nvidianimapis</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Using Xcode with 9Router as Agent</title>
      <dc:creator>eraydiler</dc:creator>
      <pubDate>Fri, 25 Sep 2026 15:05:46 +0000</pubDate>
      <link>https://dev.to/eraydiler/using-xcode-with-9router-as-agent-1a8c</link>
      <guid>https://dev.to/eraydiler/using-xcode-with-9router-as-agent-1a8c</guid>
      <description>&lt;p&gt;Xcode's coding intelligence now supports the &lt;strong&gt;Agent Client Protocol (ACP)&lt;/strong&gt;, which means you're no longer locked into Apple's default model choices — you can register any ACP-compatible coding agent, including &lt;a href="https://goose-docs.ai/" rel="noopener noreferrer"&gt;Goose&lt;/a&gt;, and point it at whatever backend you want. Combine that with &lt;strong&gt;9Router&lt;/strong&gt;, a self-hosted AI routing proxy, and you get a setup where Xcode can talk to dozens of models — with automatic fallback — through a single local endpoint.&lt;/p&gt;

&lt;p&gt;This post covers why 9Router is worth adding to your setup, then walks through wiring it up as an Xcode agent via Goose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 9Router?
&lt;/h2&gt;

&lt;p&gt;9Router sits between your coding tools and 40+ AI providers, exposing a single OpenAI-compatible endpoint (&lt;code&gt;http://localhost:20128/v1&lt;/code&gt;). A few things make it worth running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Format translation&lt;/strong&gt; — your tool speaks OpenAI's API format, 9Router translates it to whatever the underlying provider actually needs (Claude, Gemini, Vertex, GitHub Copilot, etc.). You don't have to care what format the model you're using expects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combos with fallback&lt;/strong&gt; — you can group several models into a named "combo" with a routing strategy (round robin, priority fallback, or even fusion, where multiple models are queried in parallel and a judge model picks the best answer). Point your tool at the combo name once, and 9Router handles switching models when one hits a rate limit or fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quota tracking and token savings&lt;/strong&gt; — a dashboard shows consumption per provider, and 9Router's token-saving layer trims tool-output noise (like &lt;code&gt;git diff&lt;/code&gt; or &lt;code&gt;ls&lt;/code&gt; output) before it reaches the model, which adds up fast in agentic workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-account support&lt;/strong&gt; — round-robins across multiple accounts/keys for the same provider, so you're not capped by a single subscription's quota.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: instead of hardcoding one model into every tool you use, you configure things once against 9Router and control routing centrally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Goose as an Xcode Agent
&lt;/h2&gt;

&lt;p&gt;Xcode's Intelligence settings let you register a custom agent that speaks ACP. Goose has native ACP support, so it's a natural fit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install Goose&lt;/strong&gt; and confirm the binary path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; brew &lt;span class="nb"&gt;install &lt;/span&gt;block-goose-cli
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; which goose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Register the agent in Xcode&lt;/strong&gt;&lt;br&gt;
Go to &lt;strong&gt;Xcode → Settings → Intelligence → Agents → Add an Agent&lt;/strong&gt;, and fill in:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Goose (via 9Router)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executable&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/opt/homebrew/bin/goose&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arguments&lt;/td&gt;
&lt;td&gt;&lt;code&gt;acp&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;acp&lt;/code&gt; argument is what starts Goose in ACP server mode over stdio — this is the part that's easy to get wrong, since it's not the same as &lt;code&gt;agent&lt;/code&gt; or any other subcommand name you might guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add environment variables&lt;/strong&gt; so Goose routes through 9Router instead of OpenAI directly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GOOSE_PROVIDER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openai&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GOOSE_MODEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;your model or combo name, e.g. &lt;code&gt;my-fallback-combos&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;your 9Router key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OPENAI_HOST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:20128/v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A couple of notes that took some trial and error:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Goose's OpenAI-compatible provider expects &lt;strong&gt;&lt;code&gt;OPENAI_HOST&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;OPENAI_BASE_URL&lt;/code&gt; — an easy variable name to get wrong if you're used to other tools' conventions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GOOSE_MODEL&lt;/code&gt; only accepts a single model name — it's not a list. If you want fallback across multiple models, that logic has to live on 9Router's side (as a combo), not in Goose's config.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Test in terminal first.&lt;/strong&gt; Before wiring it into Xcode, confirm the whole chain works from a plain terminal session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOOSE_PROVIDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openai &lt;span class="nv"&gt;GOOSE_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-fallback-combos &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-... &lt;span class="nv"&gt;OPENAI_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:20128/v1 &lt;span class="se"&gt;\&lt;/span&gt;
goose session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you can chat here, the 9Router connection and model resolution are solid, and Xcode should connect without issue. (Don't test with &lt;code&gt;goose acp&lt;/code&gt; directly in a terminal — it just waits silently for JSON-RPC on stdio, which looks broken but isn't.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Save the agent in Xcode&lt;/strong&gt; and select it from the coding assistant panel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Once this is wired up, switching models is just a matter of editing a combo in 9Router's dashboard or duplicating the agent entry in Xcode with a different &lt;code&gt;GOOSE_MODEL&lt;/code&gt; — no code changes, no re-authenticating each provider separately. It's a small amount of setup for a lot of flexibility.&lt;/p&gt;




</description>
      <category>xcode</category>
      <category>agents</category>
      <category>9router</category>
      <category>acp</category>
    </item>
  </channel>
</rss>
