<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Alexander van der Werff</title>
  <subtitle>Software Engineer, Entrepreneur, Music Producer</subtitle>
  <link href="https://alexandervanderwerff.com/feed.xml" rel="self"/>
  <link href="https://alexandervanderwerff.com/"/>
  <updated>2026-04-18T00:00:00Z</updated>
  <id>https://alexandervanderwerff.com/</id>
  <author>
    <n>Alexander van der Werff</n>
    <email>avdwerff@gmail.com</email>
  </author>
  <entry>
    <title>Building an Autonomous Claude Code Sandbox on macOS with Xcode MCP</title>
    <link href="https://alexandervanderwerff.com/posts/claude-code-sandbox-mcp-xcode/"/>
    <updated>2026-04-18T00:00:00Z</updated>
    <id>https://alexandervanderwerff.com/posts/claude-code-sandbox-mcp-xcode/</id>
    <content type="html">&lt;p&gt;The promise of agentic coding is compelling: describe a task, walk away, come back to a pull request. But giving an AI agent unrestricted access to your machine to make that happen is a different proposition entirely. You want the autonomy without the risk.&lt;/p&gt;
&lt;p&gt;The solution is a sandbox. A container where Claude Code can do whatever it needs, install packages, run commands, make commits, delete files, while your actual machine stays untouched. This is how we built one on macOS, and the unexpected problem we had to solve along the way.&lt;/p&gt;
&lt;h2&gt;Why not Docker Desktop?&lt;/h2&gt;
&lt;p&gt;Docker Desktop is the obvious starting point for Docker on macOS. But it requires a paid license for commercial use, runs a persistent background service you didn&#39;t ask for, and wraps everything in a GUI that gets in the way if you live in the terminal.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/abiosoft/colima&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colima&lt;/a&gt; is the alternative. It&#39;s open source, installs with &lt;code&gt;brew install colima&lt;/code&gt;, and exposes the exact same Docker socket that Docker Desktop does. Every Docker CLI command, every &lt;code&gt;docker compose&lt;/code&gt; workflow, every tool that talks to the Docker daemon, they all work identically. You just don&#39;t pay for it and it doesn&#39;t run a menubar app.&lt;/p&gt;
&lt;h2&gt;The sandbox image&lt;/h2&gt;
&lt;p&gt;The container image is straightforward. A Debian-slim Node base for Claude Code, plus the tools an autonomous agent needs: git, the GitHub CLI for opening pull requests, standard utilities, and &lt;code&gt;socat&lt;/code&gt; for a reason we&#39;ll get to.&lt;/p&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; node:20-slim&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; apt-get update &amp;amp;&amp;amp; apt-get install -y &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    git curl wget ca-certificates bash zsh &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    jq ripgrep fzf sudo socat &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# GitHub CLI&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg &amp;amp;&amp;amp; &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    echo &lt;span class=&quot;token string&quot;&gt;&quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] &#92;
    https://cli.github.com/packages stable main&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    | tee /etc/apt/sources.list.d/github-cli.list &gt; /dev/null &amp;amp;&amp;amp; &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    apt-get update &amp;amp;&amp;amp; apt-get install -y gh &amp;amp;&amp;amp; &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;
    rm -rf /var/lib/apt/lists/*&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; npm install -g @anthropic-ai/claude-code&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; mkdir -p /workspace /mnt/skills /mnt/commands&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;COPY&lt;/span&gt; entrypoint.sh /usr/local/bin/entrypoint.sh&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; chmod +x /usr/local/bin/entrypoint.sh&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WORKDIR&lt;/span&gt; /workspace&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENV&lt;/span&gt; SHELL=/bin/zsh&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENTRYPOINT&lt;/span&gt; [&lt;span class=&quot;token string&quot;&gt;&quot;/usr/local/bin/entrypoint.sh&quot;&lt;/span&gt;]&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CMD&lt;/span&gt; [&lt;span class=&quot;token string&quot;&gt;&quot;zsh&quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;entrypoint.sh&lt;/code&gt; runs at container startup and configures git identity, verifies tokens, and writes runtime config from environment variables. Secrets never get baked into the image, they come in through a &lt;code&gt;.env&lt;/code&gt; file at runtime.&lt;/p&gt;
&lt;p&gt;A shell function handles the full lifecycle:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function-name function&quot;&gt;claude-sandbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;project_path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${1&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;$PWD}&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; project_name
  &lt;span class=&quot;token assign-left variable&quot;&gt;project_name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$project_path&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;[:upper:]&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;[:lower:]&#39;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;claude-&lt;span class=&quot;token variable&quot;&gt;$project_name&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    --env-file ~/Development/Claude/sandbox/.env &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;GIT_USER_NAME&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${GIT_USER_NAME}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;GIT_USER_EMAIL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${GIT_USER_EMAIL}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$project_path&lt;/span&gt;&quot;&lt;/span&gt;:/workspace &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; ~/some-path-to-your/skills:/root/.claude/skills:ro &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; ~/some-path-to-your/commands:/root/.claude/commands:ro &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; ~/.ssh:/root/.ssh:ro &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    --add-host host.docker.internal:host-gateway &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    claude-sandbox:latest claude
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run &lt;code&gt;claude-sandbox ~/some-path-to-your/project&lt;/code&gt; and you&#39;re in an isolated Claude Code session scoped to that project. Skills and commands from your host are available read-only. SSH keys work for pushing. The container is gone when you exit.&lt;/p&gt;
&lt;h2&gt;The Xcode MCP problem&lt;/h2&gt;
&lt;p&gt;For Swift and iOS development, you want Claude Code to be able to build and run tests. Apple provides &lt;code&gt;xcrun mcpbridge&lt;/code&gt;, a stdio MCP server that bridges Claude Code directly to Xcode&#39;s tool service. Build a target, run the test suite, inspect the project structure, all from Claude Code without leaving the terminal.&lt;/p&gt;
&lt;p&gt;The problem: &lt;code&gt;xcrun mcpbridge&lt;/code&gt; is macOS-only. It talks to a running Xcode instance over a local socket. The sandbox is a Linux container. There&#39;s no way to run Xcode in there.&lt;/p&gt;
&lt;p&gt;The obvious solution is to run &lt;code&gt;xcrun mcpbridge&lt;/code&gt; on the host and expose it over HTTP so the container can reach it through &lt;code&gt;host.docker.internal&lt;/code&gt;. Tools like &lt;a href=&quot;https://github.com/sparfenyuk/mcp-proxy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mcp-proxy&lt;/a&gt; wrap any stdio MCP server in an HTTP/SSE interface.&lt;/p&gt;
&lt;p&gt;It didn&#39;t work, not because of connectivity, but because of how Claude Code handles authentication. Claude Code treats any HTTP or SSE MCP server as a remote server and requires OAuth authentication before connecting, even when the server is running on localhost. There&#39;s no way to bypass this for HTTP transport.&lt;/p&gt;
&lt;p&gt;Stdio transport has no such requirement. Claude Code connects to stdio MCP servers by spawning a local process and piping to its stdin/stdout. No authentication, no OAuth, no ceremony.&lt;/p&gt;
&lt;p&gt;So the question becomes: can we make a stdio connection reach across the container boundary?&lt;/p&gt;
&lt;h2&gt;The socat bridge&lt;/h2&gt;
&lt;p&gt;Yes, with &lt;code&gt;socat&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On the macOS host, &lt;code&gt;socat&lt;/code&gt; listens on a TCP port. For each incoming connection, it forks a new &lt;code&gt;xcrun mcpbridge&lt;/code&gt; process and bridges that process&#39;s stdin/stdout to the socket:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;socat TCP-LISTEN:3001,fork,reuseaddr EXEC:&lt;span class=&quot;token string&quot;&gt;&quot;xcrun mcpbridge&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the container, Claude Code connects to the MCP server using stdio transport. The &amp;quot;command&amp;quot; it runs is &lt;code&gt;socat&lt;/code&gt; connecting back to the host:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;xcode&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;stdio&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;socat&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TCP:host.docker.internal:3001&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From Claude Code&#39;s perspective, it spawned a local process and is piping to it. No network authentication required. The container has &lt;code&gt;socat&lt;/code&gt; installed. The host has &lt;code&gt;socat&lt;/code&gt; listening. &lt;code&gt;host.docker.internal&lt;/code&gt; routes to the Mac through Colima&#39;s virtual network.&lt;/p&gt;
&lt;p&gt;The sandbox function starts the bridge before launching the container:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;lsof&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ti&lt;/span&gt; :3001 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;xargs&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-9&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;/dev/null &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
socat TCP-LISTEN:3001,fork,reuseaddr EXEC:&lt;span class=&quot;token string&quot;&gt;&quot;xcrun mcpbridge&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; /tmp/mcp-bridge.log &lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;2&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;&amp;amp;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$!&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; /tmp/claude-sandbox-mcp-bridge.pid&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the &lt;code&gt;entrypoint.sh&lt;/code&gt; writes the &lt;code&gt;.mcp.json&lt;/code&gt; to the workspace on every start:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; /workspace/.mcp.json &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;EOF
{
  &quot;mcpServers&quot;: {
    &quot;xcode&quot;: {
      &quot;type&quot;: &quot;stdio&quot;,
      &quot;command&quot;: &quot;socat&quot;,
      &quot;args&quot;: [&quot;TCP:&lt;span class=&quot;token variable&quot;&gt;${XCODE_MCP_HOST&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;host.docker.internal}&lt;/span&gt;:&lt;span class=&quot;token variable&quot;&gt;${XCODE_MCP_PORT&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;3001}&lt;/span&gt;&quot;, &quot;-&quot;]
    }
  }
}
EOF&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After allowing the connection in Xcode once, &lt;code&gt;/mcp&lt;/code&gt; in Claude Code shows &lt;code&gt;xcode · ✔ connected&lt;/code&gt; with all 20 Xcode tools available.&lt;/p&gt;
&lt;h2&gt;Permissions without interruptions&lt;/h2&gt;
&lt;p&gt;The last piece is making the sandbox actually autonomous. By default, Claude Code asks for confirmation before every tool call. That defeats the point.&lt;/p&gt;
&lt;p&gt;Claude Code reads permissions from &lt;code&gt;settings.json&lt;/code&gt;, not from &lt;code&gt;CLAUDE.md&lt;/code&gt; (which is for instructions and context, not configuration). The &lt;code&gt;entrypoint.sh&lt;/code&gt; writes a &lt;code&gt;settings.json&lt;/code&gt; on startup:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; /root/.claude/settings.json &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;EOF&#39;
{
  &quot;permissions&quot;: {
    &quot;allow&quot;: [
      &quot;Bash(git *)&quot;,
      &quot;Bash(gh *)&quot;,
      &quot;Bash(npm *)&quot;,
      &quot;Bash(swift *)&quot;,
      &quot;Bash(xcodebuild *)&quot;,
      &quot;Bash(mkdir *)&quot;, &quot;Bash(mv *)&quot;, &quot;Bash(cp *)&quot;,
      &quot;Bash(touch *)&quot;, &quot;Bash(cat *)&quot;, &quot;Bash(ls *)&quot;,
      &quot;Bash(curl *)&quot;, &quot;Bash(claude *)&quot;,
      &quot;Read(*)&quot;, &quot;Write(*)&quot;, &quot;Edit(*)&quot;,
      &quot;WebFetch(domain:api.github.com)&quot;,
      &quot;WebFetch(domain:github.com)&quot;
    ]
  }
}
EOF&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Git operations, GitHub CLI, file reads and writes, web fetches to GitHub, all allowed without prompting. Destructive operations outside &lt;code&gt;/workspace&lt;/code&gt;, system file modifications, still require confirmation. The container is the security boundary; the permissions reflect that.&lt;/p&gt;
&lt;h2&gt;The result&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;claude-sandbox ~/some-path-to-my/ios-app&lt;/code&gt; gives you a fully isolated Claude Code session that can read and write Swift source files, make commits, open pull requests, and trigger Xcode builds and test runs, all without security prompts interrupting autonomous operation.&lt;/p&gt;
&lt;p&gt;The Xcode MCP bridge was the unexpected hard part. The intuitive solution (HTTP proxy) fails because of how Claude Code handles remote server authentication. The actual solution (socat stdio tunnel) works because it makes a networked process look like a local one.&lt;/p&gt;
&lt;p&gt;All the config lives in a single git repo at &lt;code&gt;~/some-path-to-my/Claude/sandbox/&lt;/code&gt;. No Docker Desktop, no OAuth flows for local tools.&lt;/p&gt;
&lt;p&gt;The sandbox is where Claude Code runs, your Mac is where Xcode runs and with Socat connecting them.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building Dillo: A Learning Experiment in C Interop and Agentic Coding</title>
    <link href="https://alexandervanderwerff.com/posts/building-dillo/"/>
    <updated>2026-03-23T00:00:00Z</updated>
    <id>https://alexandervanderwerff.com/posts/building-dillo/</id>
    <content type="html">&lt;p&gt;We set out to build Dillo with two questions in mind, not &amp;quot;can we make an archive app?&amp;quot; but rather: &lt;strong&gt;how well does Swift&#39;s C interop actually work?&lt;/strong&gt; And: &lt;strong&gt;can you build a real macOS app almost entirely through agentic coding?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dillo became the answer to both.&lt;/p&gt;
&lt;h2&gt;The C Interop Question&lt;/h2&gt;
&lt;p&gt;Swift is great at many things, but interfacing with battle-tested C libraries is where it gets interesting. We wanted to wrap &lt;a href=&quot;https://libarchive.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;libarchive&lt;/a&gt;, a powerful C library that handles virtually every archive format out there, and turn it into a proper Swift package. No Objective-C bridging headers, no shims. Just Swift talking to C.&lt;/p&gt;
&lt;p&gt;The key to making this work is a &lt;code&gt;module.modulemap&lt;/code&gt;, a small file that tells Swift how to import the C headers:&lt;/p&gt;
&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;module CLibArchive &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    header &lt;span class=&quot;token string&quot;&gt;&quot;archive.h&quot;&lt;/span&gt;
    header &lt;span class=&quot;token string&quot;&gt;&quot;archive_entry.h&quot;&lt;/span&gt;
    link &lt;span class=&quot;token string&quot;&gt;&quot;xml2&quot;&lt;/span&gt;
    export &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Six lines that bridge two worlds. With this in place, Swift can directly call into libarchive&#39;s C API, &lt;code&gt;archive_read_open&lt;/code&gt;, &lt;code&gt;archive_entry_pathname&lt;/code&gt;, all of it, without any wrapper code. From there, we built a Swift-idiomatic layer on top.&lt;/p&gt;
&lt;p&gt;That C bridge becomes a clean Swift API on top, here&#39;s what using SwiftArchive actually looks like:&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Extract an archive&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Archive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; archiveURL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; destinationURL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Create a ZIP from files&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Archive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;file1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; archiveURL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Encrypted archive with progress&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Archive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    files&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; files&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    to&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; archiveURL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    format&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;zip&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    encryption&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;aes256&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    password&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;secret&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; progress &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;&#92;(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastPathComponent&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;&#92;(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;progress &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Format detection&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;format&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; compression&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Archive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;detectFormat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; someURL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result is &lt;a href=&quot;https://github.com/avdwerff/swift-archive&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SwiftArchive&lt;/a&gt;, an open-source Swift package that wraps libarchive with a clean, idiomatic API. ZIP, RAR, 7z, TAR, ISO, and a dozen more formats, all accessible through Swift.&lt;/p&gt;
&lt;h2&gt;The Agentic Coding Question&lt;/h2&gt;
&lt;p&gt;The second experiment was more radical. Could we use AI-assisted agentic coding, specifically Claude Code, to go from &amp;quot;we have a Swift package&amp;quot; to &amp;quot;we have a production-ready macOS app on the App Store&amp;quot;?&lt;/p&gt;
&lt;p&gt;The answer: surprisingly, yes. The bulk of Dillo&#39;s SwiftUI interface, the drag-and-drop architecture, the accessibility implementation, even the App Store submission materials, screenshots, descriptions, privacy policy, were built in collaboration with AI. Not generated and forgotten, but iteratively developed, reviewed, and refined.&lt;/p&gt;
&lt;p&gt;What worked well: scaffolding views, generating accessibility labels and identifiers, creating marketing assets, navigating the surprisingly complex App Store submission process. What still needed a human: architectural decisions, the subtle UX choices that make a utility app feel &lt;em&gt;right&lt;/em&gt;, and debugging the occasional sandbox entitlement issue that no AI could have anticipated.&lt;/p&gt;
&lt;h2&gt;The Result&lt;/h2&gt;
&lt;p&gt;Dillo is a drag-and-drop archive utility for macOS. It compresses and extracts files, supports 15+ formats, offers AES-256 encryption, and does it all without ever connecting to the internet.&lt;/p&gt;
&lt;p&gt;But more than the app itself, Dillo validated two ideas for us: Swift&#39;s C interop and agentic coding isn&#39;t replacing developers, it&#39;s giving solo developers the bandwidth to ship things in solo mode.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://apps.apple.com/app/dillo-archive/id6760717845&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Download Dillo on the Mac App Store →&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
