<?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: Ozgur Sagiroglu</title>
    <description>The latest articles on DEV Community by Ozgur Sagiroglu (@ozgursagiroglu).</description>
    <link>https://dev.to/ozgursagiroglu</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%2F3789002%2Fde469f0f-04c7-4aec-badc-ff46fa594652.png</url>
      <title>DEV Community: Ozgur Sagiroglu</title>
      <link>https://dev.to/ozgursagiroglu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ozgursagiroglu"/>
    <language>en</language>
    <item>
      <title>My Claude Code workflows ran for days. Headless workers finished in minutes.</title>
      <dc:creator>Ozgur Sagiroglu</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:42:04 +0000</pubDate>
      <link>https://dev.to/ozgursagiroglu/my-claude-code-workflows-ran-for-days-headless-workers-finished-in-minutes-50a9</link>
      <guid>https://dev.to/ozgursagiroglu/my-claude-code-workflows-ran-for-days-headless-workers-finished-in-minutes-50a9</guid>
      <description>&lt;p&gt;I run four projects with Claude Code. For a while, my long multi-agent jobs went through Claude Code's Workflow tool, and they kept running for days. Some never finished.&lt;/p&gt;

&lt;p&gt;Part of it was effort. Every agent in a workflow runs at the effort level of the session that started it, and a workflow script has no documented per-agent effort setting. So I kept switching model and effort by hand, trying to make the big jobs move. That became a job of its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup that worked
&lt;/h2&gt;

&lt;p&gt;I split the roles.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One interactive Claude Code session is the orchestrator.&lt;/strong&gt; It plans the work, writes a narrow brief for each part, launches it, reads the report and decides what happens next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each part runs as a headless &lt;code&gt;claude -p&lt;/code&gt; worker&lt;/strong&gt; in its own git worktree, with its own tool list, model, effort and budget. Workers run in parallel as separate processes and report back as JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing lands without gates and a human yes.&lt;/strong&gt; Push and deploy stay mine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A worker launch ends up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;--output-format&lt;/span&gt; stream-json &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; claude-opus-5-5 &lt;span class="nt"&gt;--effort&lt;/span&gt; high &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; dontAsk &lt;span class="nt"&gt;--permission-prompts&lt;/span&gt; none &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--setting-sources&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;--strict-mcp-config&lt;/span&gt; &lt;span class="nt"&gt;--restricted&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tools&lt;/span&gt; &lt;span class="s2"&gt;"Read,Edit,Write,Glob,Grep,Bash"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--settings&lt;/span&gt; ./worker-settings.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-budget-usd&lt;/span&gt; 10 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;lt; brief.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a settings file that turns on the sandbox and spells out the allow and deny rules:&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sandbox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;span class="nl"&gt;"failIfUnavailable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;span class="nl"&gt;"autoAllowBashIfSandboxed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowUnsandboxedCommands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"filesystem"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"denyWrite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/abs/path/to/repo/.git"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Bash(pnpm test)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(pnpm typecheck)"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Read(**/.env*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(**/*.pem)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(~/.ssh/**)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&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;Most of those flags are there because something went wrong without them. More on that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;Same work, both ways, from my own logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7 research tasks: a Workflow run had finished none of them after 73 minutes. As headless workers, all 7 finished within about 7 minutes.&lt;/li&gt;
&lt;li&gt;A batch that had dragged on for 16 hours without finishing: 6 workers, 45 minutes, 6 of 6 done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a controlled benchmark. The Workflow run was stopped at the 73-minute mark, and 5 of the 7 headless workers started from digests of the stopped work. The two that didn't were still among the fastest.&lt;/p&gt;

&lt;p&gt;It also seems to use less of my usage limits for the same work, probably because each role runs at the effort it needs instead of everything at max. I haven't measured that properly yet, and I'll post the numbers when I have them.&lt;/p&gt;

&lt;h2&gt;
  
  
  10 things that bit me
&lt;/h2&gt;

&lt;p&gt;All of this is as of Claude Code 2.1.295.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;--allowedTools&lt;/code&gt; doesn't restrict anything.&lt;/strong&gt; It pre-approves. Tools that never ask for approval still run: with only &lt;code&gt;--allowedTools&lt;/code&gt; set, my workers spawned &lt;code&gt;Agent&lt;/code&gt; subagents and called &lt;code&gt;Monitor&lt;/code&gt; and &lt;code&gt;ToolSearch&lt;/code&gt; with zero denials. &lt;code&gt;--tools&lt;/code&gt; sets which tools exist at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Prefix rules match the command as written.&lt;/strong&gt; &lt;code&gt;Bash(git push *)&lt;/code&gt; doesn't match &lt;code&gt;git -C repo push&lt;/code&gt;. A &lt;code&gt;curl&lt;/code&gt; deny does nothing against &lt;code&gt;node -e "fetch(...)"&lt;/code&gt;. And &lt;code&gt;Bash(pnpm *)&lt;/code&gt; means "run any code", via &lt;code&gt;pnpm exec&lt;/code&gt; or &lt;code&gt;pnpm dlx&lt;/code&gt;. A denylist is a speed bump. The OS sandbox is the wall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Turning on the sandbox auto-approves Bash.&lt;/strong&gt; &lt;code&gt;autoAllowBashIfSandboxed&lt;/code&gt; defaults to true, and an allow rule can approve an unsandboxed retry unless &lt;code&gt;allowUnsandboxedCommands&lt;/code&gt; is false. Set both, plus &lt;code&gt;failIfUnavailable&lt;/code&gt;, so a missing sandbox fails loudly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Worktrees leave the shared &lt;code&gt;.git&lt;/code&gt; writable.&lt;/strong&gt; Code a worker runs inside a git worktree can write loose refs and files into the main repository's &lt;code&gt;.git&lt;/code&gt;. &lt;code&gt;sandbox.filesystem.denyWrite&lt;/code&gt; on that directory closes it, and &lt;code&gt;git status&lt;/code&gt;, &lt;code&gt;log&lt;/code&gt; and &lt;code&gt;diff&lt;/code&gt; still work. Let your launcher commit, not the worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Workers inherit your settings unless you cut them off.&lt;/strong&gt; Without &lt;code&gt;--setting-sources ""&lt;/code&gt;, a worker loads your user allow rules, plugin hooks, MCP servers, auto-memory and effort level. In an untrusted workspace, &lt;code&gt;-p&lt;/code&gt; drops the project's allow rules with only a stderr warning, while all of that still loads. Cutting the sources has two side effects: effort falls to the CLI default (medium for Opus 5.5, not high), and your CLAUDE.md stops loading. Pass &lt;code&gt;--effort&lt;/code&gt; every time, and inline your project rules into the brief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Started from an editor, a worker looks like an editor session.&lt;/strong&gt; It inherits the IDE's environment, registers as an interactive session and accepts messages from other sessions. Build the worker's environment from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. A usage-limit stop looks like success.&lt;/strong&gt; The result says &lt;code&gt;subtype: "success"&lt;/code&gt;, with &lt;code&gt;is_error: true&lt;/code&gt; and exit code 1. Classify by &lt;code&gt;api_error_status: 429&lt;/code&gt; and &lt;code&gt;terminal_reason&lt;/code&gt; instead. Pre-assign &lt;code&gt;--session-id&lt;/code&gt; so you can &lt;code&gt;--resume&lt;/code&gt; after the reset. A cold resume re-pays the context, though: one resume cost about $18 (API-equivalent) for 63 tool calls, against $9 for the first 79.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. &lt;code&gt;--output-format json&lt;/code&gt; can describe only the last turn.&lt;/strong&gt; If the worker used a background task, every task notification starts a new turn, and the final JSON covers just that one. Duration and turn counts go wrong; cost stays cumulative. Use &lt;code&gt;stream-json&lt;/code&gt; and keep every &lt;code&gt;result&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. A stopped worker can hang forever.&lt;/strong&gt; One worker hit the usage limit while holding a background &lt;code&gt;next dev&lt;/code&gt; server, and its process never exited. I found it 1 hour 49 minutes later. Give each worker its own process group, a wall-clock and an idle timeout, and reap its children.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. A smoke test has to prove a tool call.&lt;/strong&gt; An old CLI on my PATH ran a smoke test on its default model and answered "OK" with a commit SHA it made up: 1.8 seconds, no tool call. The same CLI passed my haiku preflights, then failed every Opus run in 3 seconds. Preflight with the exact binary, model and flags your workers will use, and check that a real tool call happened.&lt;/p&gt;

&lt;p&gt;One more: Claude Code's Bash checks refuse some command shapes no matter what your rules say, like &lt;code&gt;cmd; echo $?&lt;/code&gt;, &lt;code&gt;cd x &amp;amp;&amp;amp; ...&lt;/code&gt; and &lt;code&gt;sleep 60; ...&lt;/code&gt;. Tell workers to use single, foreground commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I packaged it
&lt;/h2&gt;

&lt;p&gt;For a while every project session wrote its own launch scripts: five sessions, five different setups for the same method, and the gotchas above kept coming back. So I turned it into a Claude Code plugin called arbab. The name means master, the one who gives the orders: your session is the arbab, and the workers do the work.&lt;/p&gt;

&lt;p&gt;It's a skill that teaches your session the method, plus a runner with zero runtime dependencies that does the mechanics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role fences built from the flags and settings above;&lt;/li&gt;
&lt;li&gt;quota holds, and auto-resume of the same session after a usage limit;&lt;/li&gt;
&lt;li&gt;status per lane, measured from the stream and the transcript: turns, tool calls, refusals, effort, cost;&lt;/li&gt;
&lt;li&gt;integrate, verify (your gates, run inside the fence), review (lens reviewers plus skeptics for MEDIUM+ findings), a readiness report, and land. It never pushes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built arbab with arbab. Its first three milestones took 146 worker attempts and 5,091 tool calls, and only 1.3% of those calls were refused by the fence. My hand-made fences had refused 5.8 to 15%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin marketplace add ozgursagiroglu/arbab
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;arbab@arbab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It needs Claude Code 2.1.290 or newer, Node 20 and git, and it's only tested on macOS so far. Keep in mind that N parallel workers use your quota about N times as fast as one.&lt;/p&gt;

&lt;p&gt;The repo is at &lt;a href="https://github.com/ozgursagiroglu/arbab" rel="noopener noreferrer"&gt;github.com/ozgursagiroglu/arbab&lt;/a&gt;. If you try it, I'd like to hear what breaks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
    <item>
      <title>I built a production-ready Shopify Hydrogen theme and open-sourced it</title>
      <dc:creator>Ozgur Sagiroglu</dc:creator>
      <pubDate>Sun, 16 Aug 2026 23:40:54 +0000</pubDate>
      <link>https://dev.to/ozgursagiroglu/i-built-a-production-ready-shopify-hydrogen-theme-and-open-sourced-it-1891</link>
      <guid>https://dev.to/ozgursagiroglu/i-built-a-production-ready-shopify-hydrogen-theme-and-open-sourced-it-1891</guid>
      <description>&lt;p&gt;Every Hydrogen project I've worked on started the same way — wire up a cart, build a PDP, add filters, then spend weeks on the "extras" that aren't really extras: wishlist, compare, quick view, proper i18n, RTL.&lt;/p&gt;

&lt;p&gt;After doing this enough times I decided to build it once and properly.&lt;/p&gt;

&lt;p&gt;ada ÉLAN is a Hydrogen storefront theme for fashion brands. It ships:&lt;/p&gt;

&lt;p&gt;— An editorial design system (Cormorant Garamond + Plus Jakarta Sans, documented design tokens)&lt;br&gt;
— Real i18n — English, French, Arabic with full RTL layout&lt;br&gt;
— Merchandising surfaces: lookbook, shop-the-look, compare, wishlist, quick view, reviews&lt;br&gt;
— A seeding CLI that provisions metaobjects and demo products so your store isn't empty on first run&lt;br&gt;
— Unit tests (Vitest), E2E across 5 browsers (Playwright), Storybook component docs&lt;/p&gt;

&lt;p&gt;Stack: Hydrogen 2025.7, React Router 7, Tailwind 4, TypeScript strict, Zustand, Framer Motion.&lt;/p&gt;

&lt;p&gt;The seeding CLI is probably the part I'm most proud of. You run one command and it creates all the metaobject definitions, uploads demo content, and provisions products. No more manual setup in the Shopify admin.&lt;/p&gt;

&lt;p&gt;MIT licensed. Feedback welcome.&lt;/p&gt;

&lt;p&gt;github.com/ozgursagiroglu/shopify-hydrogen-fashion-theme&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>hydrogen</category>
      <category>opensource</category>
      <category>ecommerce</category>
    </item>
  </channel>
</rss>
