<?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: Bobby Chugani</title>
    <description>The latest articles on DEV Community by Bobby Chugani (@guestarai).</description>
    <link>https://dev.to/guestarai</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%2F4089289%2F2c2cd109-8637-4676-b39d-5f89a35797f5.png</url>
      <title>DEV Community: Bobby Chugani</title>
      <link>https://dev.to/guestarai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/guestarai"/>
    <language>en</language>
    <item>
      <title>We run our whole growth engine on one VPS. Here's the cron table.</title>
      <dc:creator>Bobby Chugani</dc:creator>
      <pubDate>Sat, 22 Aug 2026 08:28:19 +0000</pubDate>
      <link>https://dev.to/guestarai/we-run-our-whole-growth-engine-on-one-vps-heres-the-cron-table-4h4</link>
      <guid>https://dev.to/guestarai/we-run-our-whole-growth-engine-on-one-vps-heres-the-cron-table-4h4</guid>
      <description>&lt;p&gt;We sell AI systems that run an independent business's growth: content, search, social,&lt;br&gt;
outreach, follow-up. The obvious question a buyer asks is whether we run the thing we&lt;br&gt;
sell on ourselves. We do, and it is all cron. Here is the whole machine.&lt;/p&gt;

&lt;p&gt;70 active cron jobs on one VPS. Not 70 tasks in a project management tool. 70 lines in&lt;br&gt;
&lt;code&gt;crontab -l&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually runs
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/5  * * * *   cal-reminder.py            calendar bookings -&amp;gt; Telegram
15   * * * *   signup-monitor.py          new signups -&amp;gt; Telegram
30   * * * *   ig-inbox-monitor.py        Instagram DMs
0    */4 * * * cli-canary.sh              is the LLM path still alive
30   */2 * * * cron-heartbeat.sh          is anything silently dead
0    6 * * *   platform-pulse.sh          daily health report
0    9 * * 1-5 email-outreach.py          cold email, weekdays only
0    10 * * 1-5 email-followup.py         the follow-up sequence
0    4,16 * * * segment-lead-finder.py    lead discovery, twice daily
0    9 * * *   ig-engagement.py           Instagram engagement
30   3 * * *   backup.sh                  nightly DB dump, offsite
0    9 * * 1   seo-audit.sh               weekly SEO audit
0    7 * * 2   competitor-monitor.sh      competitor pages
0    8 * * 3   ai-discoverability.sh      are we cited by AI assistants
0    5 * * 1   seonaut-crawl.py           full site crawl
0    17 * * 0  gen_posts.sh 7             a week of social, generated
0    6 * * 2   gen_audit_reel.sh          video, weekly
30   3 * * 3   gen_podcast.sh             podcast episode, weekly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Plus blog publishing three times a day, location page generation, revenue attribution,&lt;br&gt;
review monitoring, and a second brand's worth of the same running alongside.&lt;/p&gt;

&lt;p&gt;Note how many of those are &lt;code&gt;flock&lt;/code&gt; + &lt;code&gt;timeout&lt;/code&gt;. Those are load-bearing, see below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that cost us real money to learn
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A &lt;code&gt;flock -n&lt;/code&gt; and a hung script is weeks of silent death.&lt;/strong&gt; A job hangs holding the lock.&lt;br&gt;
Every subsequent run exits immediately because the lock is held, cleanly, with status 0.&lt;br&gt;
Nothing alerts. The signature is an empty log file next to a fat rotated one from before&lt;br&gt;
the wedge. Every lock line now has a &lt;code&gt;timeout&lt;/code&gt; on it, and a heartbeat job checks that each&lt;br&gt;
log is younger than its expected interval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A dead search API does not throw, it returns an empty list.&lt;/strong&gt; Our egress IP got flagged.&lt;br&gt;
Every search engine started returning zero results instead of an error. Nine scripts kept&lt;br&gt;
running: the cold email personaliser wrote from an empty context string, the lead finder&lt;br&gt;
reported "no leads in this segment" for segments it had never actually searched, and the&lt;br&gt;
blog generator was told to look up statistics, got nothing back, and wrote the post from&lt;br&gt;
the model's own memory anyway. Nothing errored for weeks.&lt;/p&gt;

&lt;p&gt;The fix was one line of philosophy applied everywhere: &lt;strong&gt;an empty result and a broken tool&lt;br&gt;
must be different return values.&lt;/strong&gt; Our search wrapper now returns an error, not &lt;code&gt;[]&lt;/code&gt;, when&lt;br&gt;
no provider answered, and callers branch on the error rather than on the length.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The auth token expires and everything just stops.&lt;/strong&gt; Our scripts shell out to a CLI. When&lt;br&gt;
its OAuth token lapses, every script fails fast and quietly, and cron jobs complete in&lt;br&gt;
seconds with no output. Now there is a canary job whose only purpose is to make one call&lt;br&gt;
every four hours and shout if it fails.&lt;/p&gt;

&lt;p&gt;The pattern in all three: the failure mode was &lt;em&gt;silence&lt;/em&gt;, not an error. Everything I would&lt;br&gt;
now build differently is about making silence impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it work
&lt;/h2&gt;

&lt;p&gt;We do not publish traffic or revenue figures, so I am not going to make some up for a&lt;br&gt;
post. What I can point at is checkable:&lt;/p&gt;

&lt;p&gt;One inbound enquiry this week found us with no outbound touch at all: a guesthouse in&lt;br&gt;
South Africa that booked a call through the site. We ran our own free tool against their&lt;br&gt;
domain and found two things. Their web host had suspended the site, which they did not&lt;br&gt;
know. And five booking platforms outranked their own domain for their own business name,&lt;br&gt;
so every returning guest searching for them by name was landing on a channel that takes a&lt;br&gt;
commission. That report went out the same day.&lt;/p&gt;

&lt;p&gt;One enquiry is not a trend and I am not going to dress it up as one. It is the first&lt;br&gt;
thing the machine has produced that I did not have to go and fetch.&lt;/p&gt;

&lt;p&gt;The systems that found that, wrote it and sent it are the same 70 lines above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools
&lt;/h2&gt;

&lt;p&gt;One VPS. Postgres in Docker. Python and Node scripts. &lt;code&gt;cron&lt;/code&gt;. &lt;code&gt;flock&lt;/code&gt;. &lt;code&gt;systemd&lt;/code&gt; for the&lt;br&gt;
few things that need to be services. No queue, no Kubernetes, no orchestrator. The most&lt;br&gt;
complicated piece of infrastructure is a connection pooler.&lt;/p&gt;

&lt;p&gt;The scoring engine behind our free site checker is open source if you want to read how one&lt;br&gt;
of these actually works: github.com/guestarAI/findable. MIT, no dependencies.&lt;/p&gt;

&lt;p&gt;Happy to answer anything about the setup.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cron</category>
      <category>automation</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Running the Claude CLI as a production backend when the API won't take your token</title>
      <dc:creator>Bobby Chugani</dc:creator>
      <pubDate>Sat, 22 Aug 2026 07:29:16 +0000</pubDate>
      <link>https://dev.to/guestarai/running-the-claude-cli-as-a-production-backend-when-the-api-wont-take-your-token-2lh3</link>
      <guid>https://dev.to/guestarai/running-the-claude-cli-as-a-production-backend-when-the-api-wont-take-your-token-2lh3</guid>
      <description>&lt;p&gt;If you pay for a Claude Max subscription and try to reuse that OAuth token against&lt;br&gt;
&lt;code&gt;api.anthropic.com&lt;/code&gt;, you get a 404. Not a 401, which would at least tell you what&lt;br&gt;
happened. The subscription and the API are separate products with separate billing,&lt;br&gt;
and the token from one is not currency in the other.&lt;/p&gt;

&lt;p&gt;That leaves you with a real cost decision. Either you pay twice, once for the&lt;br&gt;
subscription your team already uses and again per-token for anything you build, or&lt;br&gt;
you find a way to use the subscription you have. This post is about the second&lt;br&gt;
option: running &lt;code&gt;claude --print&lt;/code&gt; as a subprocess behind a small HTTP service, and&lt;br&gt;
the four things that broke when I put it in production.&lt;/p&gt;

&lt;p&gt;I have three of these running on one VPS now. They have been up for months. None of&lt;br&gt;
this is subtle, but every one of these took a while to work out from a symptom that&lt;br&gt;
did not obviously point at the cause.&lt;/p&gt;
&lt;h2&gt;
  
  
  The shape
&lt;/h2&gt;

&lt;p&gt;A systemd unit runs a small Node server. It spawns the Claude CLI per request with&lt;br&gt;
&lt;code&gt;--print&lt;/code&gt;, captures stdout, and returns it. The public app calls that service over a&lt;br&gt;
shared secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--print&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--output-format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--no-session-persistence&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--system-prompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CLAUDE_BIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SANDBOX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pipe&lt;/span&gt;&lt;span class="dl"&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;&lt;code&gt;--output-format json&lt;/code&gt; is worth taking. You get a wrapper object with the result plus&lt;br&gt;
&lt;code&gt;total_cost_usd&lt;/code&gt;, so you can log what each request would have cost and keep an honest&lt;br&gt;
picture of what the subscription is actually saving you.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. The CLI's own auto-updater deletes the binary you are executing
&lt;/h2&gt;

&lt;p&gt;This one produced the best bug of the year. Intermittent &lt;code&gt;EACCES&lt;/code&gt; on spawn, maybe&lt;br&gt;
one request in a few hundred, never reproducible on demand.&lt;/p&gt;

&lt;p&gt;The Claude CLI updates itself. It replaces &lt;code&gt;/usr/local/bin/claude&lt;/code&gt; in place. If a&lt;br&gt;
request happens to spawn during that swap, the file you are executing is gone&lt;br&gt;
mid-execution. Under normal interactive use you would never notice. Under a&lt;br&gt;
per-request service, you notice.&lt;/p&gt;

&lt;p&gt;The fix is to stop executing the file that updates itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /usr/local/bin/claude&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/myservice/bin/claude"&lt;/span&gt;
&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;.new"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;          &lt;span class="c"&gt;# atomic replace, no window where dst is missing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point the service at the private copy and refresh it deliberately when you want a&lt;br&gt;
newer CLI. &lt;code&gt;DISABLE_AUTOUPDATER=1&lt;/code&gt; in the unit environment is worth setting too, but&lt;br&gt;
the private copy is the part that actually closes the race, because the updater you&lt;br&gt;
are disabling is not always the one that runs.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. &lt;code&gt;CLAUDECODE&lt;/code&gt; leaks into child processes
&lt;/h2&gt;

&lt;p&gt;If anything in your chain was itself launched from a Claude Code session, the&lt;br&gt;
environment carries &lt;code&gt;CLAUDECODE=1&lt;/code&gt;. A CLI spawned underneath sees it, believes it is&lt;br&gt;
nested inside another session, and misbehaves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLAUDECODE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IS_SANDBOX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// required if the service runs as root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is nasty because it works perfectly when you test it by hand from your own&lt;br&gt;
shell and fails only when triggered through the path that matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Bulk loops hit a burst limit that looks exactly like a code bug
&lt;/h2&gt;

&lt;p&gt;Fire &lt;code&gt;claude --print&lt;/code&gt; back to back and the first ten to twenty-five calls succeed,&lt;br&gt;
then everything fails until it cools off. I found this the expensive way: a&lt;br&gt;
ninety-page batch job produced eleven pages and seventy-nine consecutive failures,&lt;br&gt;
and a single call by hand minutes later worked fine. So not auth, not quota. Pure&lt;br&gt;
throttling.&lt;/p&gt;

&lt;p&gt;If you are doing bulk work: pace it, back off and retry two or three times, and make&lt;br&gt;
failures re-queueable so a transient throttle does not consume the work item. And log&lt;br&gt;
&lt;code&gt;e.stderr&lt;/code&gt;, not &lt;code&gt;e.message&lt;/code&gt;. &lt;code&gt;execFileSync&lt;/code&gt; puts the entire command line in the&lt;br&gt;
message, and with a long prompt that turns your log into unreadable noise. That&lt;br&gt;
detail hid the real cause from me for an embarrassingly long time.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Turn the tools off if the input is untrusted
&lt;/h2&gt;

&lt;p&gt;One of my services takes a URL from the public internet, fetches the page, and asks&lt;br&gt;
the model to analyse it. The page content is a stranger's text arriving inside my&lt;br&gt;
prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// empty allowlist, every built-in tool disabled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model gets pre-fetched evidence and nothing else. It cannot fetch, cannot read&lt;br&gt;
files, cannot be talked into either by a page that includes instructions addressed to&lt;br&gt;
it. Everything the model needs is gathered by the Node process first and passed in as&lt;br&gt;
data. If you take one thing from this post, take this one: prompt-level instructions&lt;br&gt;
to ignore injected text are a request, not a control. An empty tool allowlist is a&lt;br&gt;
control.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one that surprised me: the CLI as a search provider
&lt;/h2&gt;

&lt;p&gt;My server's IP is flagged. Every search engine I could reach from it either serves a&lt;br&gt;
CAPTCHA or, worse, serves a page that looks like results and is not. I needed to know&lt;br&gt;
whether a business ranks for its own name, and had no working way to ask.&lt;/p&gt;

&lt;p&gt;The CLI has WebSearch built in, and it runs on Anthropic's infrastructure, so my IP&lt;br&gt;
never enters the picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--print&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-haiku-4-5-20251001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--no-session-persistence&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--allowedTools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WebSearch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--permission-mode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acceptEdits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both flags are needed. Without &lt;code&gt;--allowedTools WebSearch&lt;/code&gt; a non-interactive session&lt;br&gt;
refuses the tool and tells you to grant permission in settings, which is not&lt;br&gt;
actionable from a daemon.&lt;/p&gt;

&lt;p&gt;It takes thirty to ninety seconds, so it is a fallback rather than a primary. But it&lt;br&gt;
costs nothing on a subscription I already pay for, and it works where four scraping&lt;br&gt;
approaches did not.&lt;/p&gt;

&lt;p&gt;A warning attached to that, since it nearly shipped. Before I found this, I tried&lt;br&gt;
scraping Bing with a headless browser. It returned HTTP 200, the correct page title,&lt;br&gt;
my query echoed back in the HTML, and ten well-formed result rows whose hrefs&lt;br&gt;
base64-decoded cleanly. Every structural check passed. The decoded URLs pointed at a&lt;br&gt;
children's clothing retailer on one run and French anime sites on the next: a&lt;br&gt;
convincing SERP shell full of unrelated ads, served because the site did not trust&lt;br&gt;
me. &lt;strong&gt;Assert that results contain your query's own terms.&lt;/strong&gt; A row count proves&lt;br&gt;
nothing at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this a good idea
&lt;/h2&gt;

&lt;p&gt;For internal tooling, batch work and anything where a slow path is acceptable: yes.&lt;br&gt;
It is the difference between a line item and no line item.&lt;/p&gt;

&lt;p&gt;For latency-critical user-facing requests: no. Process spawn plus model time is&lt;br&gt;
seconds, not milliseconds.&lt;/p&gt;

&lt;p&gt;And read your plan terms rather than taking my word for what yours permits. I am&lt;br&gt;
describing what worked, not making a claim about what you are allowed to do.&lt;/p&gt;

&lt;p&gt;The site checker I built on top of this is open source and MIT, if you want to see&lt;br&gt;
the shape in full: &lt;a href="https://github.com/guestarAI/findable" rel="noopener noreferrer"&gt;github.com/guestarAI/findable&lt;/a&gt;.&lt;br&gt;
The scoring engine there has no LLM in it at all, which is rather the point. The&lt;br&gt;
model writes the explanation; the deterministic part does the looking.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>devops</category>
      <category>systemd</category>
    </item>
  </channel>
</rss>
