<?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: Devin</title>
    <description>The latest articles on DEV Community by Devin (@devinios).</description>
    <link>https://dev.to/devinios</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%2F4207621%2F9ef45a16-1de6-411a-b71f-fb5d3ed4643f.png</url>
      <title>DEV Community: Devin</title>
      <link>https://dev.to/devinios</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devinios"/>
    <language>en</language>
    <item>
      <title>Wiring an ACP agent into Xcode: the field notes</title>
      <dc:creator>Devin</dc:creator>
      <pubDate>Mon, 26 Oct 2026 15:47:43 +0000</pubDate>
      <link>https://dev.to/devinios/wiring-an-acp-agent-into-xcode-the-field-notes-3k3c</link>
      <guid>https://dev.to/devinios/wiring-an-acp-agent-into-xcode-the-field-notes-3k3c</guid>
      <description>&lt;p&gt;On 2026-10-26 we wired Claude Code into Xcode 27.1 (build 25183.120.5) using the built-in ACP support. Xcode's &lt;strong&gt;Add an ACP Agent&lt;/strong&gt; dialog asks for five fields: Name, Executable, Interpreter, Arguments, and Environment Variables. No plugin, no project file: point Xcode at one executable and get the handshake right.&lt;/p&gt;

&lt;p&gt;These notes cover what we ran, what came back, and four failures. Everything below was verified in a terminal. We did not verify the GUI session end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ACP is doing inside Xcode
&lt;/h2&gt;

&lt;p&gt;ACP is the Agent Client Protocol. An editor acts as the client, driving an agent over JSON-RPC on stdio. Xcode 27.1 ships the client side as &lt;code&gt;IDEIntelligenceACP.framework&lt;/code&gt; under &lt;code&gt;Xcode.app/Contents/PlugIns/&lt;/code&gt;. It sends protocol requests and does not care what is on the other end.&lt;/p&gt;

&lt;p&gt;The Claude Code CLI does not speak ACP. A small adapter translates ACP requests into something Claude Code understands and returns the result as ACP. The package you install is the adapter, not Claude Code itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: install the adapter under its current name
&lt;/h2&gt;

&lt;p&gt;The old package is deprecated. Check it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm view @zed-industries/claude-code-acp version deprecated
&lt;span class="c"&gt;# 0.16.2&lt;/span&gt;
&lt;span class="c"&gt;# This package has been renamed to @agentclientprotocol/claude-agent-acp.&lt;/span&gt;
&lt;span class="c"&gt;# Please migrate to continue receiving updates.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the new one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @agentclientprotocol/claude-agent-acp
which claude-agent-acp
&lt;span class="c"&gt;# /Users/&amp;lt;your-username&amp;gt;/.npm-global/bin/claude-agent-acp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your npm prefix differs, run &lt;code&gt;npm prefix -g&lt;/code&gt; and look under &lt;code&gt;&amp;lt;prefix&amp;gt;/bin/&lt;/code&gt;. Our machine had 0.95.0 installed; the latest on npm at the time was 0.97.0.&lt;/p&gt;

&lt;p&gt;Do not put &lt;code&gt;npx&lt;/code&gt; in the Executable field. Xcode starts the agent in something closer to a clean shell; &lt;code&gt;npx&lt;/code&gt; may download the package on the spot, stop at a prompt, or resolve a different version. A global install gives you one fixed entry point.&lt;/p&gt;

&lt;p&gt;Confirm what npm produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lL&lt;/span&gt; ~/.npm-global/bin/claude-agent-acp
file ~/.npm-global/bin/claude-agent-acp
&lt;span class="c"&gt;# a /usr/bin/env node script text executable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a &lt;code&gt;#!/usr/bin/env node&lt;/code&gt; script, not a native binary. That decides how you fill in Interpreter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: fill the five fields
&lt;/h2&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;th&gt;Why&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;Claude Agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display name; avoid an existing agent's name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executable&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/Users/&amp;lt;your-username&amp;gt;/.npm-global/bin/claude-agent-acp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Absolute path; &lt;code&gt;~&lt;/code&gt; is not guaranteed to expand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interpreter&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;leave empty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The script has a shebang; the kernel finds node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arguments&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;leave empty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The adapter reads ACP from stdio and needs no flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment Variables&lt;/td&gt;
&lt;td&gt;usually empty&lt;/td&gt;
&lt;td&gt;Only needed if node is missing from Xcode's inherited PATH&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Interpreter is the field people overfill. Xcode's help says to specify one only when the executable is a script that needs it; Xcode then runs the executable as an argument to that interpreter. This adapter already starts with &lt;code&gt;#!/usr/bin/env node&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Point Executable at the &lt;code&gt;bin/&lt;/code&gt; entry, not &lt;code&gt;lib/node_modules/.../dist/index.js&lt;/code&gt;; the &lt;code&gt;bin/&lt;/code&gt; symlink is npm-managed, while the &lt;code&gt;dist&lt;/code&gt; path can move.&lt;/p&gt;

&lt;p&gt;Arguments only appends flags. This adapter needs none, and extra values can be read as unknown options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: probe the handshake before opening Xcode
&lt;/h2&gt;

&lt;p&gt;The first ACP request is &lt;code&gt;initialize&lt;/code&gt;. It does not create a session or read project files; it exchanges protocol version and capabilities. A small Python script proves the adapter launches:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&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;jsonrpc&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;2.0&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&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;initialize&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;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;protocolVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clientCapabilities&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;fs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;readTextFile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;writeTextFile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terminal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&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;clientInfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;name&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;acp-probe&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;version&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;0.1.0&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popen&lt;/span&gt;&lt;span class="p"&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;/Users/&amp;lt;your-username&amp;gt;/.npm-global/bin/claude-agent-acp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&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="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bufsize&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="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="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&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;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&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="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&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;terminate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ACP uses line-delimited JSON-RPC: one request per line, one response per line. You should see one JSON line immediately. This is the real response from the new package, version 0.95.0, on our machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"protocolVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"agentCapabilities"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"claudeCode"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"promptQueueing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"authStatus"&lt;/span&gt;&lt;span class="p"&gt;:{}},&lt;/span&gt;&lt;span class="nl"&gt;"promptCapabilities"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"embeddedContext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"mcpCapabilities"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"logout"&lt;/span&gt;&lt;span class="p"&gt;:{}},&lt;/span&gt;&lt;span class="nl"&gt;"providers"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"loadSession"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sessionCapabilities"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"additionalDirectories"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"close"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"fork"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"resume"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"systemPrompt"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"override"&lt;/span&gt;&lt;span class="p"&gt;:{}}}}},&lt;/span&gt;&lt;span class="nl"&gt;"agentInfo"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"@agentclientprotocol/claude-agent-acp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Claude Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0.95.0"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"authMethods"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"steering"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the same script against the old path, &lt;code&gt;~/.npm-global/bin/claude-code-acp&lt;/code&gt;, and the capability block differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The old package's &lt;code&gt;sessionCapabilities&lt;/code&gt; has only &lt;code&gt;fork&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, and &lt;code&gt;resume&lt;/code&gt;; the new one adds &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;additionalDirectories&lt;/code&gt;, and &lt;code&gt;systemPrompt.mode.override&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The new package adds &lt;code&gt;claudeCode.promptQueueing&lt;/code&gt;, &lt;code&gt;authStatus&lt;/code&gt;, &lt;code&gt;auth.logout&lt;/code&gt;, and &lt;code&gt;providers&lt;/code&gt; under &lt;code&gt;agentCapabilities&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The old package returns one &lt;code&gt;authMethods&lt;/code&gt; entry, &lt;code&gt;claude-login&lt;/code&gt;; the new package returns &lt;code&gt;[]&lt;/code&gt; and expresses login state through &lt;code&gt;authStatus&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These fields are version-specific; do not treat this JSON as a contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the response
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;promptCapabilities.image&lt;/code&gt; and &lt;code&gt;embeddedContext&lt;/code&gt;: the client may send images and embedded context; whether Xcode does is separate.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcpCapabilities.http&lt;/code&gt; and &lt;code&gt;sse&lt;/code&gt;: the adapter accepts those MCP transports, not that it connects on its own.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loadSession: true&lt;/code&gt; and &lt;code&gt;sessionCapabilities&lt;/code&gt;: reload and session operations. &lt;code&gt;authMethods: []&lt;/code&gt; means no ACP login method; check &lt;code&gt;authStatus&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together these identify the installed binary without trusting the version string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting the four failures
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Quarantine or permissions.&lt;/strong&gt; Check extended attributes first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xattr &lt;span class="nt"&gt;-l&lt;/span&gt; ~/.npm-global/bin/claude-agent-acp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;com.apple.quarantine&lt;/code&gt; is present, remove it with &lt;code&gt;xattr -d com.apple.quarantine &amp;lt;path&amp;gt;&lt;/code&gt; and retry. On our machine the only attribute was &lt;code&gt;com.apple.provenance&lt;/code&gt; — the provenance "ghost" attribute, not quarantine — and Gatekeeper did not block it. If Xcode raises a Files and Folders or Full Disk Access prompt, the prompt is usually attributed to Xcode, the parent process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Node is not on PATH.&lt;/strong&gt; The shebang uses the PATH inherited by the process, not the PATH after you &lt;code&gt;source ~/.zshrc&lt;/code&gt;. &lt;code&gt;which node&lt;/code&gt; working in your terminal does not prove Xcode can see it. Add &lt;code&gt;PATH&lt;/code&gt; under Environment Variables and leave Interpreter empty, or set Interpreter to node's absolute path while Executable stays the script. Check first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which node        &lt;span class="c"&gt;# for example /usr/local/bin/node&lt;/span&gt;
node &lt;span class="nt"&gt;--version&lt;/span&gt;    &lt;span class="c"&gt;# our machine: v24.12.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. The script is not executable.&lt;/strong&gt; Inspect the symlink and its target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; ~/.npm-global/bin/claude-agent-acp
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lL&lt;/span&gt; ~/.npm-global/bin/claude-agent-acp
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; ~/.npm-global/bin/claude-agent-acp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;executable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;file&lt;/code&gt; says "script text executable" but &lt;code&gt;test -x&lt;/code&gt; fails, the permission bits were lost. Run &lt;code&gt;chmod +x&lt;/code&gt; on the target file. A normal npm global install is 755; upgrades and manual copies break this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The handshake times out.&lt;/strong&gt; The Python probe blocks on &lt;code&gt;readline()&lt;/code&gt;. If nothing comes back: run the executable directly and see whether it exits with an error; confirm &lt;code&gt;node --version&lt;/code&gt; works; confirm you are pointing at the &lt;code&gt;bin/&lt;/code&gt; entry and not a stale path from the old package. &lt;code&gt;initialize&lt;/code&gt; does not require login. If a later session reports an unauthenticated state, run &lt;code&gt;claude&lt;/code&gt; once in a terminal. "Xcode shows nothing" is not a diagnosis; the terminal probe is the minimal reproduction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we judged success
&lt;/h2&gt;

&lt;p&gt;The terminal bar is simple: &lt;code&gt;result.agentInfo.name&lt;/code&gt; matches the package you installed, &lt;code&gt;protocolVersion&lt;/code&gt; is 1, and &lt;code&gt;agentCapabilities&lt;/code&gt; is not empty. That proves the command Xcode will launch can start, read a request, and answer over ACP.&lt;/p&gt;

&lt;p&gt;We then saved the configuration and created a session, but did not verify every GUI behavior, so we are not vouching for it. Once the handshake passes, the remaining problems tend to be PATH, permissions, or login.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope and caveats
&lt;/h2&gt;

&lt;p&gt;Based on our own setup on 2026-10-26; Apple has not documented ACP in Xcode beyond the dialog. The commands and JSON response come from that machine's output, not a vendor walkthrough. ACP is changing quickly. Run the &lt;code&gt;initialize&lt;/code&gt; probe against your installed version before debugging the dialog.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>xcode</category>
      <category>ai</category>
      <category>tooling</category>
    </item>
    <item>
      <title>App Store review is backed up — what a developer can actually do this week</title>
      <dc:creator>Devin</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:13:02 +0000</pubDate>
      <link>https://dev.to/devinios/app-store-review-is-backed-up-what-a-developer-can-actually-do-this-week-2oap</link>
      <guid>https://dev.to/devinios/app-store-review-is-backed-up-what-a-developer-can-actually-do-this-week-2oap</guid>
      <description>&lt;p&gt;App Review is running weeks behind for some developers: through late October 2026, threads on Apple's developer forums describe apps stuck in "Waiting for Review" for three weeks, a month, and in one reported case since 19 September. The delays are concentrated in first submissions of new apps and in resubmissions after a rejection; developers shipping routine updates to established apps are mostly still seeing the usual day or two.&lt;/p&gt;

&lt;p&gt;The advice differs by queue. Here is what is documented, what developers report, and what can change your week.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, separate the average from the tail
&lt;/h2&gt;

&lt;p&gt;Per Apple's documentation: "App Review works around the clock with the goal of reviewing at least 50% of submissions in less than 24 hours and 90% in less than 48 hours."&lt;/p&gt;

&lt;p&gt;That is a target, not a promise for your submission. The useful context came from Apple's response to MobileGamer.biz on 20 October: over the previous 12 weeks, App Review processed an average of 378,000 submissions per week, with an average review time of 1.5 days for all apps and 14 hours for minor updates. Apple also said some new apps take longer when they need additional scrutiny, and that it had prioritized updates to existing apps, expanded automation, strengthened abuse detection, and added safeguards against repeated submissions with the same unresolved issues.&lt;/p&gt;

&lt;p&gt;An average of 1.5 days is compatible with a long tail of three-week waits. The Information's April report of an 84% year-on-year jump in submissions is repeated by both outlets; it is secondhand, not an Apple metric.&lt;/p&gt;

&lt;p&gt;Developers report: "Reviews often take 3–5 days, even when expedited, while feedback is frequently vague and appears not to reflect our app descriptions or testing instructions," one studio boss told MobileGamer.biz. The Register collected forum posts titled "Stuck in Waiting for Review for about 3 weeks" and "Stuck in 'Waiting for Review' since October 8"; one developer said they had been waiting since 19 September. MobileGamer.biz also spoke to a studio with a five-year-old game that had seen no change in update review times. The pattern is new apps and post-rejection resubmissions, not routine updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven actions for this week
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Classify your submission before you do anything else.&lt;/strong&gt;&lt;br&gt;
A first submission of a new app, an update to a live app, and a resubmission after a rejection are not the same queue in practice. The reported pain is in the first and third. If you are in the update queue and within 48 hours, wait; if you are a new app at day seven, start the escalation path below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Do not resubmit just to bump the queue.&lt;/strong&gt;&lt;br&gt;
Per Apple's documentation, a resubmission starts a new review rather than moving the existing one up; in the Bug Fix flow it says: "Do not resubmit the app at this time, as it will trigger a new review." Apple also told MobileGamer.biz that it had added safeguards against repeated submissions with the same unresolved issues, which is a strong hint that churn does not help.&lt;br&gt;
Developers report: uploading replacement builds while waiting can move you to the back of the line. Apple has not documented this; treat it as a risk. One submission, then use the documented channels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use expedited review only if you meet one of two conditions.&lt;/strong&gt;&lt;br&gt;
Per Apple's documentation, only submissions already sent to App Review can be expedited, and the request goes through "Request an Expedited App Review" with the app and platform selected. The eligible reasons are a critical bug fix or a release that must align with a dated event. For a bug, put the specifics in the Notes field; for an event, give the event, its date, and how the app relates to it.&lt;br&gt;
Two constraints are easy to miss. Apple decides "as allowed by availability and demand", and it warns that "Expedite requests should be reserved for matters of genuine urgency." A request that says "we would like to launch sooner" is not one of the two categories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. If you were rejected, reply before you resubmit.&lt;/strong&gt;&lt;br&gt;
Per Apple's documentation, "Using App Store Connect to contact App Review is the best way to receive assistance with an ongoing submission", and a specialist familiar with your app will reply. Be specific about the feature, guideline, or step. Do not use this window to appeal or to request an expedited review; Apple says to use the appropriate Contact Us form for those.&lt;br&gt;
A reply that tends to get a useful answer looks like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build 1.4.2 (318) is the submission under review. The feature you flagged is at Settings &amp;gt; Export &amp;gt; CSV. Demo account: &lt;a href="mailto:review@example.com"&gt;review@example.com&lt;/a&gt;. The backend flag export_v2 is enabled for that account. Attached are a 40-second screen recording and a point-by-point mapping to Guideline 2.1. Could you confirm whether this resolves the issue?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you need a call, request it in the same window with your time zone and language. Apple says calls typically complete within three to five business days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Check the rejection for the Bug Fix Submission offer.&lt;/strong&gt;&lt;br&gt;
Per Apple's documentation, if the message begins "Bug Fix Submissions: The issues we've identified below are eligible to be resolved on your next update", you can reply to accept the current submission and fix the issues next time. The same page is explicit: "Do not resubmit the app at this time, as it will trigger a new review." All deferred issues must be fixed in the next update, and the same issue cannot be deferred twice in a row.&lt;br&gt;
This is the fastest documented path for a live app with a fixable metadata problem. It does not cover legal or safety issues, and it is not available for a first submission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Appeal once, and only after clarification fails.&lt;/strong&gt;&lt;br&gt;
Per Apple's documentation, an appeal to the App Review Board is for three situations: you believe a rejected or removed app follows the guidelines; your account faces termination but you believe it complies; or you believe the review was unfair or biased. Reply first, then appeal if the clarification does not resolve it. Submit one appeal per app, address every cited violation in that one appeal, and note that the form accepts up to five apps. Do not use an appeal to ask for a status update, an expedite, or reinstatement of an already-terminated account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Build the review packet now, not after the rejection.&lt;/strong&gt;&lt;br&gt;
The cheapest delay to remove is a reviewer having to guess. Put a demo account or demo mode, the exact path to each flagged feature, backend flags or regional dependencies, and a short screen recording in the Notes field before submission. If a feature needs a server state that is hard to reach, say how to reach it. It does not speed up the queue, but it can cut a rejection loop from three round-trips to one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-day plan
&lt;/h2&gt;

&lt;p&gt;Monday: classify the submission as new, update, or resubmission. Tuesday: rewrite the notes and record the demo path. Wednesday: submit the expedited request if you qualify, or send one precise reply. Thursday: fix what you can without a new build. Friday: reassess. One follow-up is reasonable; a daily ping is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What not to do
&lt;/h2&gt;

&lt;p&gt;Do not file an appeal as a status check. Do not request an expedite without a critical bug or a dated event. Do not resubmit an unchanged binary and expect a different queue. Do not treat the 1.5-day average as a commitment. And do not assume "AI-generated" is a rejection reason: Apple says every app is held to the same standards; the backlog is about volume, not a ban.&lt;/p&gt;

&lt;p&gt;None of this guarantees approval or shortens the queue. It keeps your submission from losing another week to a process mistake.&lt;/p&gt;

&lt;p&gt;If you're already in a rejection loop, I packaged the 10-step 48-hour checklist and the two Resolution Center reply templates into one download. Get it here — &lt;a href="https://devinspire734.gumroad.com/l/asr-response-kit-free" rel="noopener noreferrer"&gt;App Store Rejection Response Kit — Free&lt;/a&gt; (free, email required).&lt;/p&gt;

&lt;p&gt;A full checklist with source links is here: &lt;a href="https://hhfa008.github.io/personal/app-store-rescue.html" rel="noopener noreferrer"&gt;https://hhfa008.github.io/personal/app-store-rescue.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>indiehackers</category>
      <category>tips</category>
    </item>
    <item>
      <title>CocoaPods Is Going Read-Only: Migrating a 20+ Dependency iOS App to Swift Package Manager</title>
      <dc:creator>Devin</dc:creator>
      <pubDate>Mon, 26 Oct 2026 11:37:54 +0000</pubDate>
      <link>https://dev.to/devinios/cocoapods-is-going-read-only-migrating-a-20-dependency-ios-app-to-swift-package-manager-32a7</link>
      <guid>https://dev.to/devinios/cocoapods-is-going-read-only-migrating-a-20-dependency-ios-app-to-swift-package-manager-32a7</guid>
      <description>&lt;p&gt;Your &lt;code&gt;Podfile&lt;/code&gt; still installs. That is exactly why this is easy to postpone — and why a project with twenty-plus dependencies should start now rather than in December.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what actually changes
&lt;/h2&gt;

&lt;p&gt;CocoaPods trunk is moving to a read-only state. In the official words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"December 2nd 2026 — I will switch trunk to not accept new Podspecs permanently."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And from Firebase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Firebase 12 is the final major version published to CocoaPods, and Firebase will stop publishing new versions to CocoaPods in October 2026."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read-only is not shut down. Existing pod versions stay downloadable, the Specs repo and CDN keep serving, and &lt;code&gt;pod install&lt;/code&gt; keeps resolving what it resolves today. CocoaPods is explicit about this: "Making the switch will not break builds for people using CocoaPods in 2026 onwards." Firebase says the same about its own packages: existing versions "in CocoaPods will remain installable and functional as long as CocoaPods ecosystem support continues."&lt;/p&gt;

&lt;p&gt;What you lose after December is the ability to receive &lt;em&gt;new&lt;/em&gt; versions through trunk. For an app that ships quarterly, that is a slow, undramatic problem — the worst kind, because nothing pages you at 3 a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024-11-30&lt;/td&gt;
&lt;td&gt;CocoaPods announces the plan: "In two years we plan to turn CocoaPods trunk to be read-only."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid–late 2025&lt;/td&gt;
&lt;td&gt;First email to everyone who contributed a Podspec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep–Oct 2026&lt;/td&gt;
&lt;td&gt;Second email, roughly one month before the test run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 2026&lt;/td&gt;
&lt;td&gt;Firebase stops publishing to CocoaPods; Firebase 12 is the last major version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 1–7, 2026&lt;/td&gt;
&lt;td&gt;Test run of read-only, "giving automation a chance to break early"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dec 2, 2026&lt;/td&gt;
&lt;td&gt;Trunk permanently stops accepting new Podspecs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After&lt;/td&gt;
&lt;td&gt;Existing versions remain installable; Specs repo and CDN keep serving&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CocoaPods adds a caveat worth quoting: "These dates are not set in stone." Treat the December date as a planning deadline, not a hard API cutoff. Flutter has already moved in the same direction — starting with Flutter 3.44, Swift Package Manager replaces CocoaPods as the default for iOS and macOS apps — so the direction of travel is not in question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Inventory and freeze
&lt;/h2&gt;

&lt;p&gt;Do this on a branch, and commit the current working state before you touch anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--show-toplevel&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; spm-migration

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*pod "&lt;/span&gt; Podfile | &lt;span class="nb"&gt;tee&lt;/span&gt; /tmp/pods.txt
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &amp;lt; /tmp/pods.txt

git add Podfile Podfile.lock
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"chore: snapshot CocoaPods state before SPM migration"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then go through &lt;code&gt;/tmp/pods.txt&lt;/code&gt; line by line and mark each dependency: has a Swift package, has a binary xcframework, or has neither. The third bucket decides your schedule. Twenty pods where eighteen migrate cleanly is a week; three pods with no package support can eat a month.&lt;/p&gt;

&lt;p&gt;One thing people miss: &lt;strong&gt;CocoaPods subspecs do not map one-to-one onto Swift packages.&lt;/strong&gt; A pod like &lt;code&gt;Vendor/Core&lt;/code&gt; and &lt;code&gt;Vendor/UI&lt;/code&gt; may become two products of a single package, or one product with everything included. Read the vendor's own SPM instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Deintegrate
&lt;/h2&gt;

&lt;p&gt;Quit Xcode first. &lt;code&gt;pod deintegrate&lt;/code&gt; rewrites the project file, and an open Xcode window will happily overwrite those changes when it saves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pod deintegrate
pod clean

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; archive/cocoapods
git &lt;span class="nb"&gt;mv &lt;/span&gt;Podfile archive/cocoapods/Podfile
git &lt;span class="nb"&gt;mv &lt;/span&gt;Podfile.lock archive/cocoapods/Podfile.lock

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; Pods &lt;span class="k"&gt;*&lt;/span&gt;.xcworkspace
git status &lt;span class="nt"&gt;--short&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Archive, do not delete. The &lt;code&gt;Podfile.lock&lt;/code&gt; is your rollback artifact for the rest of the migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add the packages
&lt;/h2&gt;

&lt;p&gt;For an app target, the path of least resistance is Xcode: open the &lt;code&gt;.xcodeproj&lt;/code&gt;, choose File → Add Package Dependencies, paste the repository URL, pick the products, and confirm they are added to the target's "Frameworks, Libraries, and Embedded Content."&lt;/p&gt;

&lt;p&gt;If your app is split into local Swift packages, declare the dependency in &lt;code&gt;Package.swift&lt;/code&gt; instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// swift-tools-version: 6.0&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;PackageDescription&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"AppCore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;platforms&lt;/span&gt;&lt;span class="p"&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;iOS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v17&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="p"&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;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/firebase/firebase-ios-sdk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"12.0.0"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nv"&gt;targets&lt;/span&gt;&lt;span class="p"&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;target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"AppCore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="p"&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;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"FirebaseAuth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"firebase-ios-sdk"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"FirebaseFirestore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"firebase-ios-sdk"&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="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;Commit &lt;code&gt;Package.resolved&lt;/code&gt; alongside &lt;code&gt;Package.swift&lt;/code&gt;. It is the SPM equivalent of &lt;code&gt;Podfile.lock&lt;/code&gt;, and without it your CI and your laptop can resolve different revisions of the same branch-based dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Clean the leftovers
&lt;/h2&gt;

&lt;p&gt;CocoaPods leaves more behind than the &lt;code&gt;Pods/&lt;/code&gt; directory. Check for:&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="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"PODS_ROOT&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;Pods/"&lt;/span&gt; App.xcodeproj/project.pbxproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are looking for build settings pointing at &lt;code&gt;${PODS_ROOT}&lt;/code&gt;, and build phases named &lt;code&gt;[CP] Check Pods Manifest.lock&lt;/code&gt;, &lt;code&gt;[CP] Embed Pods Frameworks&lt;/code&gt;, and &lt;code&gt;[CP] Copy Pods Resources&lt;/code&gt;. Remove them.&lt;/p&gt;

&lt;p&gt;Keep the Crashlytics dSYM upload run script, but note that Firebase documents a different script path for SPM checkouts than for CocoaPods installs. Copy the current version from the Crashlytics setup page rather than editing the old path by hand. A stale path here fails silently: uploads just stop, and you find out weeks later when a crash report has no symbols.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: CI
&lt;/h2&gt;

&lt;p&gt;The failure mode in CI is almost always cache, not code. Old caches still contain a &lt;code&gt;Pods/&lt;/code&gt; directory and a stale workspace that no longer exists in the repo.&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;Resolve packages&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;rm -rf ~/Library/Caches/CocoaPods&lt;/span&gt;
    &lt;span class="s"&gt;rm -rf ~/Library/Caches/org.swift.swiftpm&lt;/span&gt;
    &lt;span class="s"&gt;xcodebuild -resolvePackageDependencies \&lt;/span&gt;
      &lt;span class="s"&gt;-project App.xcodeproj \&lt;/span&gt;
      &lt;span class="s"&gt;-scheme App \&lt;/span&gt;
      &lt;span class="s"&gt;-clonedSourcePackagesDirPath "$PWD/.spm-cache"&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;Build&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;xcodebuild -project App.xcodeproj \&lt;/span&gt;
      &lt;span class="s"&gt;-scheme App \&lt;/span&gt;
      &lt;span class="s"&gt;-destination 'generic/platform=iOS' \&lt;/span&gt;
      &lt;span class="s"&gt;-clonedSourcePackagesDirPath "$PWD/.spm-cache" \&lt;/span&gt;
      &lt;span class="s"&gt;-onlyUsePackageVersionsFromResolvedFile \&lt;/span&gt;
      &lt;span class="s"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache &lt;code&gt;.spm-cache&lt;/code&gt; with the hash of &lt;code&gt;Package.resolved&lt;/code&gt; as the key, and drop any cache keyed on &lt;code&gt;Podfile.lock&lt;/code&gt;. &lt;code&gt;-onlyUsePackageVersionsFromResolvedFile&lt;/code&gt; makes CI fail loudly on a resolution mismatch instead of quietly pulling something newer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Verify, then keep the exit open
&lt;/h2&gt;

&lt;p&gt;Run a clean build from an empty derived data directory, then exercise the paths that dependency managers actually break: login, a database read, push registration, a crash upload, and one analytics event. Those five cover most of what a migration silently damages.&lt;/p&gt;

&lt;p&gt;Rollback stays cheap for as long as you keep the branch: &lt;code&gt;git revert&lt;/code&gt; the migration commit, restore &lt;code&gt;archive/cocoapods/&lt;/code&gt;, and &lt;code&gt;pod install&lt;/code&gt;. Once you delete that archive, rollback becomes a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three pitfalls that cost real time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Private pods.&lt;/strong&gt; A private pod becomes a Swift package hosted in a private Git repository. Xcode will prompt for credentials locally; CI needs an SSH key or a token wired into git. If the private pod ships as a prebuilt binary, it cannot become a normal package — it becomes a &lt;code&gt;.binaryTarget&lt;/code&gt;, and the artifact must be an xcframework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binary dependencies.&lt;/strong&gt; For a local artifact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;binaryTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"VendorSDK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Vendor/VendorSDK.xcframework"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a remote one you need both &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;checksum&lt;/code&gt;, and the checksum must be recomputed every time the artifact changes. Plain &lt;code&gt;.framework&lt;/code&gt; files and static &lt;code&gt;.a&lt;/code&gt; libraries are not accepted; they have to be repackaged as an xcframework first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource bundles.&lt;/strong&gt; CocoaPods &lt;code&gt;resource_bundles&lt;/code&gt; become SPM resources on the target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"DesignSystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;resources&lt;/span&gt;&lt;span class="p"&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;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Resources"&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;Use &lt;code&gt;.process&lt;/code&gt; for images and asset catalogs so they get optimized, &lt;code&gt;.copy&lt;/code&gt; for directories whose structure must be preserved. Access them with &lt;code&gt;Bundle.module&lt;/code&gt; — and remember that &lt;code&gt;Bundle.module&lt;/code&gt; only exists inside the package that declares the resources. If the app needs to reach them, expose a small accessor from the package instead of referring to &lt;code&gt;Bundle.module&lt;/code&gt; from app code, which will not compile.&lt;/p&gt;

&lt;h2&gt;
  
  
  One judgment
&lt;/h2&gt;

&lt;p&gt;This is a registry freeze, not a shutdown: nothing you shipped breaks on December 2. But a twenty-dependency migration is a multi-week job with a binary-artifact worst case, and the November test run is when other people's automation finds its bugs. If CocoaPods is still in your &lt;code&gt;Podfile&lt;/code&gt;, put the branch on the calendar this month.&lt;/p&gt;




&lt;p&gt;More iOS migration write-ups and project notes: &lt;a href="https://hhfa008.github.io/personal/cases.html" rel="noopener noreferrer"&gt;https://hhfa008.github.io/personal/cases.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>cocoapods</category>
      <category>migration</category>
    </item>
  </channel>
</rss>
