<?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: 健太 橘</title>
    <description>The latest articles on DEV Community by 健太 橘 (@ko-hi).</description>
    <link>https://dev.to/ko-hi</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%2F4093871%2Fb2ad41cc-40a4-486e-90e5-a7fc21c098c8.png</url>
      <title>DEV Community: 健太 橘</title>
      <link>https://dev.to/ko-hi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ko-hi"/>
    <language>en</language>
    <item>
      <title>Google's OAuth 'Testing' mode expires refresh tokens in 7 days. Publish the consent screen before you schedule anything.</title>
      <dc:creator>健太 橘</dc:creator>
      <pubDate>Wed, 16 Sep 2026 22:30:18 +0000</pubDate>
      <link>https://dev.to/ko-hi/googles-oauth-testing-mode-expires-refresh-tokens-in-7-days-publish-the-consent-screen-before-24hm</link>
      <guid>https://dev.to/ko-hi/googles-oauth-testing-mode-expires-refresh-tokens-in-7-days-publish-the-consent-screen-before-24hm</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — If your Google Cloud OAuth consent screen is still in &lt;strong&gt;Testing&lt;/strong&gt;, every refresh token it issues dies after seven days. That is documented behavior, not a bug. My unattended YouTube uploader ran fine for a week and then stopped at 06:45 on a Saturday. The fix is not "re-authenticate"; it is "publish the app", and publishing has prerequisites that took an hour, not a minute.&lt;/p&gt;




&lt;p&gt;You scheduled it. It ran for a week. Then it stopped.&lt;/p&gt;

&lt;p&gt;That is the whole shape of this failure, and if you have a cron job, a GitHub Action, or a Task Scheduler entry that talks to a Google API with a refresh token you minted on your laptop, it is probably sitting in your future too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;My YouTube Shorts pipeline is the one part of my setup that runs with nobody watching: Claude Code writes the script, a renderer makes the video, and a PowerShell runner uploads it at 14:00 and 21:00 every day. On September 12 at 06:45 the refresh token expired. It was exactly seven days old.&lt;/p&gt;

&lt;p&gt;The same morning, before the first scheduled slot, I added a small script called &lt;code&gt;check_youtube_token.js&lt;/code&gt; in front of the runner. It tries to refresh the token; if that fails, the runner writes &lt;code&gt;SKIP: waiting for YouTube re-auth&lt;/code&gt; to its log and exits without launching anything. So the outage was loud rather than silent: three skipped slots that day, two more the next, five lost uploads in total, each with the reason next to it. The next morning's self-check surfaced the same line.&lt;/p&gt;

&lt;p&gt;But a machine cannot click "Allow" on a Google sign-in page. The five slots stayed lost until a human sat down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why: this is Google's documented behavior
&lt;/h2&gt;

&lt;p&gt;From the OAuth 2.0 docs, under &lt;a href="https://developers.google.com/identity/protocols/oauth2#expiration" rel="noopener noreferrer"&gt;refresh token expiration&lt;/a&gt;: a Google Cloud project whose OAuth consent screen is configured for an external user type and has a publishing status of &lt;strong&gt;Testing&lt;/strong&gt; is issued a refresh token that expires in seven days.&lt;/p&gt;

&lt;p&gt;Read that again if you built your integration the way most tutorials show. You create a project, set up the consent screen, add yourself as a test user, run the local auth flow once, save &lt;code&gt;token.json&lt;/code&gt;, and schedule the job. Everything in that sequence leaves the app in Testing. The token works on day one. It works on day six. On day seven it does not, and nothing in the auth flow warned you.&lt;/p&gt;

&lt;p&gt;Re-authenticating buys you another seven days and nothing else. If that is your fix, you will be doing it every week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: put the consent screen in production
&lt;/h2&gt;

&lt;p&gt;The real fix is changing the publishing status from Testing to &lt;strong&gt;In production&lt;/strong&gt;. In my case the "Publish app" button was greyed out, and here is why, in the order I hit it:&lt;/p&gt;

&lt;p&gt;Google requires three branding fields before an external app can be published: an application homepage URL, a privacy policy URL, and at least one authorized domain. The authorized domain has to be one you have proven you own, and the proof Google accepts is Search Console verification. My domain was not verified yet, so the branding form would not save, so the publish button would not enable.&lt;/p&gt;

&lt;p&gt;So the actual sequence was: open Search Console, add the domain as a property, verify it by uploading an HTML file to the site root, go back to the consent screen, fill in the three branding fields, save, click Publish app, then run the auth flow one more time so the new refresh token is issued under production status. The re-auth itself took about a minute. The rest took the better part of an hour, starting before 5 a.m., because the pipeline had already been down for two days.&lt;/p&gt;

&lt;p&gt;After that, &lt;code&gt;check_youtube_token.js&lt;/code&gt; reports the app as production with no expiry on the token, and the 14:00 slot that afternoon uploaded normally.&lt;/p&gt;

&lt;p&gt;One thing to check before you assume you are safe: the token response tells you. When the app is in Testing, the JSON that comes back from the auth flow includes a &lt;code&gt;refresh_token_expires_in&lt;/code&gt; field counting down from seven days. If you see that field, you are on the clock.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in the runner
&lt;/h2&gt;

&lt;p&gt;Two things, and neither is clever.&lt;/p&gt;

&lt;p&gt;First, the token check runs &lt;em&gt;before&lt;/em&gt; the expensive part. The runner used to launch Claude Code first and let the upload step, at the very end, discover the dead token. Now the check is the first thing that happens; if it fails, the run costs nothing and the log says exactly why.&lt;/p&gt;

&lt;p&gt;Second, "skip with a reason" is treated as a first-class outcome, not a failure to hide. A row that says &lt;code&gt;SKIP: waiting for YouTube re-auth&lt;/code&gt; is more useful than a row that says &lt;code&gt;OK&lt;/code&gt; when nothing was uploaded, and a lot more useful than a stack trace at the bottom of a long log. The daily self-check reads those rows and puts the human task at the top of the to-do list.&lt;/p&gt;

&lt;p&gt;If you are building anything unattended on Google APIs, the order is: verify the domain, fill in branding, publish the consent screen, &lt;em&gt;then&lt;/em&gt; run the auth flow, &lt;em&gt;then&lt;/em&gt; schedule. Doing it in the other order works for exactly one week.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;My take: Google should refuse to issue a refresh token to a Testing app at all, instead of letting it die silently on day seven.&lt;/strong&gt; Tell me why I'm wrong — or tell me which of your scheduled jobs is still running on one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A Japanese version of this post goes up the same day on &lt;a href="https://note.com/holy_owl9373" rel="noopener noreferrer"&gt;my note.com blog&lt;/a&gt;.&lt;/em&gt; The runner with the pre-launch token check is &lt;a href="https://tachibana53.gumroad.com/l/claude-code-unattended-runner" rel="noopener noreferrer"&gt;free, pay what you want, on Gumroad&lt;/a&gt; — I own the store; that is a plain product link.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>discuss</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>7 Claude Code agents held 24 board meetings. Revenue: $0.</title>
      <dc:creator>健太 橘</dc:creator>
      <pubDate>Sun, 13 Sep 2026 08:42:22 +0000</pubDate>
      <link>https://dev.to/ko-hi/7-claude-code-agents-held-24-board-meetings-revenue-0-2e6c</link>
      <guid>https://dev.to/ko-hi/7-claude-code-agents-held-24-board-meetings-revenue-0-2e6c</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — For 21 days I let seven Claude Code subagents run ten small businesses from a &lt;code&gt;/board-meeting&lt;/code&gt; slash command: research, decide, write, design, publish, review, repeat. They held 24 meetings, made 382 commits, and shipped 16 articles, 18 Shorts, 3 digital products and 5 job proposals. I edited one article by hand; everything else went out as written. Total revenue: $0. Here is exactly what broke, with the numbers, because the failures are more reusable than the setup.&lt;/p&gt;




&lt;p&gt;I am one person with a Claude Code subscription. On August 24 I wrote seven agent definitions — &lt;code&gt;ceo&lt;/code&gt;, &lt;code&gt;secretary&lt;/code&gt;, &lt;code&gt;researcher&lt;/code&gt;, &lt;code&gt;writer&lt;/code&gt;, &lt;code&gt;marketer&lt;/code&gt;, &lt;code&gt;designer&lt;/code&gt;, &lt;code&gt;programmer&lt;/code&gt; — and one slash command that calls them in order. Every morning Windows Task Scheduler runs that command unattended. Fourteen other PowerShell runners post the outputs: a YouTube Short at 14:00 and 21:00, a note.com article at 07:20, a Dev.to crosspost, an X reply draft, a stats pull, a daily self-check.&lt;/p&gt;

&lt;p&gt;The output side of that machine works. Here is the input side, three weeks in:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;channel&lt;/th&gt;
&lt;th&gt;shipped&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;note.com (Japanese blog)&lt;/td&gt;
&lt;td&gt;16 articles&lt;/td&gt;
&lt;td&gt;137 views, 17 likes, total&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YouTube Shorts&lt;/td&gt;
&lt;td&gt;18 videos&lt;/td&gt;
&lt;td&gt;12,124 views&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev.to&lt;/td&gt;
&lt;td&gt;3 posts&lt;/td&gt;
&lt;td&gt;92 views&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;10 posts&lt;/td&gt;
&lt;td&gt;1 visitor sent to the blog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;freelance proposals&lt;/td&gt;
&lt;td&gt;5 sent&lt;/td&gt;
&lt;td&gt;0 replies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;digital products (Gumroad, Fiverr, BOOTH)&lt;/td&gt;
&lt;td&gt;3 listed&lt;/td&gt;
&lt;td&gt;0 sales&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every number is from the platform dashboards, pulled by a script. Nothing is rounded up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break 1: "success" that produced nothing, three times
&lt;/h2&gt;

&lt;p&gt;The runner that launches each unattended command wrote &lt;code&gt;OK&lt;/code&gt; to its log when the process exited 0 and printed something. That definition of success failed three separate ways in three weeks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A byte-order mark.&lt;/strong&gt; PowerShell 5.1 read a UTF-8 file with no BOM as the system code page, mangled the Japanese command name, and exited 0. Three days of &lt;code&gt;OK&lt;/code&gt;. Zero Shorts posted. The tell, in hindsight: log start and end were the same second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Published" meant "saved as draft".&lt;/strong&gt; The note.com poster reported success. The article sat unpublished until the agent that reads the dashboard noticed 0 views on a URL that did not exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reply writer that wrote no file.&lt;/strong&gt; The X-reply command returned 0 with a non-empty stdout. The file it was supposed to write, &lt;code&gt;replies/2026-09-13.md&lt;/code&gt;, does not exist. The secretary agent then read the &lt;code&gt;OK&lt;/code&gt; and marked the automation "live".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix is boring and I should have started with it: &lt;strong&gt;judge a run by its artifact, not its exit code.&lt;/strong&gt; The runner now takes the path of the file the command is supposed to produce and checks that it exists, was modified after the run started, and contains the heading the command always writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$ExpectedOutput&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="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$MustContain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'## 1.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$started&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# ... run the slash command ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ExpectedOutput&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="nv"&gt;$ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ExpectedOutput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Get-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ExpectedOutput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastWriteTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Select-String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ExpectedOutput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Pattern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$MustContain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Quiet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ok&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="n"&gt;Write-Log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'NG'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'no artifact'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;A &lt;code&gt;0&lt;/code&gt; from a process means the process did not crash. It does not mean the work happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break 2: a five-minute human task stopped the machine for days
&lt;/h2&gt;

&lt;p&gt;The Shorts pipeline is the only fully unattended business. It stopped on September 12 at 06:45 because the YouTube refresh token expired. Why: the Google Cloud OAuth consent screen was still in &lt;strong&gt;Testing&lt;/strong&gt;, and in Testing, refresh tokens expire after seven days.&lt;/p&gt;

&lt;p&gt;The agents detected it correctly. They put "re-authenticate and publish the OAuth app" at the top of my daily to-do, with a time estimate (five minutes) and a warning that re-authenticating alone would fail again on the 19th. What they cannot do is click &lt;strong&gt;Publish app&lt;/strong&gt; in the Google Cloud console. So the pipeline that posts two videos a day lost five slots waiting for my five minutes.&lt;/p&gt;

&lt;p&gt;If you are automating anything on Google APIs: publish the consent screen &lt;em&gt;before&lt;/em&gt; you schedule the job. And count the human clicks per dollar in your pipeline. Mine were the critical path and I had not measured them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break 3: the meeting optimized what it could measure
&lt;/h2&gt;

&lt;p&gt;Twenty-four meetings is a lot of deciding. What did they decide? Decision files with numbered items and "refutation conditions". Edits to the agent definitions. Guardrails on guardrails. A runner that checks the runners.&lt;/p&gt;

&lt;p&gt;Meanwhile, 20 X posts sat drafted in a queue from September 6, waiting for a human to press the button, because the X API costs money and the card was declined. The agents measured "runs OK" and "articles published" — the things they could touch — and polished those. Nobody in the meeting could touch the money, so the money did not move.&lt;/p&gt;

&lt;p&gt;An agent loop will get very good at the metric it can observe. If the only metric it can observe is its own process, that is what it will improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break 4: every marketplace already had agents in it
&lt;/h2&gt;

&lt;p&gt;The plan for near-term cash was freelance platforms. The rule for accepting a job was strict and honest: only work an agent can complete end to end, no calls, no design taste, no video editing. Fine. This morning the agent read 73 new listings on a Japanese platform, found 9 that qualified, then fetched each detail page for the proposal count: &lt;strong&gt;12 to 210 proposals each.&lt;/strong&gt; The one job we did apply to had 107 proposals when we sent it and 165 a week later.&lt;/p&gt;

&lt;p&gt;I also checked GitHub bounty issues as an alternative. One open issue has 1,394 comments.&lt;/p&gt;

&lt;p&gt;"My agent can do this task" is not an edge when every other freelancer's agent can do it too. It is the entry fee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break 5: one unattended meeting ate the five-hour window in 37 minutes
&lt;/h2&gt;

&lt;p&gt;Claude Code's subscription has a rolling usage window. An unattended board meeting with seven agents, each reading the whole strategy file, burned through it in 37 minutes on September 8. Everything else scheduled that morning waited hours. The fix was two start guards on the meeting runner — never twice in a day, never within 12 hours of the last full run — and splitting the minutes-writing step into its own session so a cutoff loses the least important part.&lt;/p&gt;

&lt;p&gt;Rate-limit your own agents. The scarce resource is not the model's ability; it is your plan's window.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually worked, so this is not only a list of failures
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The production side is real. Fifteen of the 16 articles and all 18 videos went out untouched; the pipeline from script to voice to render to upload runs unattended when the token is valid.&lt;/li&gt;
&lt;li&gt;The daily self-check that compares "what was scheduled" to "what left evidence" is the single most useful thing built. It caught breaks 1 and 2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest failure reports were the only content that moved.&lt;/strong&gt; The most-read article on the Japanese blog is titled "3 proposals, 0 orders". The only Dev.to post here that got comments is the one where the AI reviewer's fix was wrong. This post is written in that shape on purpose.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you are running agents unattended, check these first
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Define "done" as an artifact — a file, a URL that returns 200, a row in a table — never as an exit code.&lt;/li&gt;
&lt;li&gt;Check the artifact's modification time against the run's start time. A stale file from yesterday passes a plain existence test.&lt;/li&gt;
&lt;li&gt;List every step a human must click, and put a time estimate next to each. Those are your outages waiting to happen.&lt;/li&gt;
&lt;li&gt;Put OAuth apps in production before the first scheduled run.&lt;/li&gt;
&lt;li&gt;Give the loop one metric it cannot fake. Views, replies, dollars. Not "runs OK".&lt;/li&gt;
&lt;li&gt;Count the competition before you count on the marketplace. Fetch the proposal count; do not trust the listing page.&lt;/li&gt;
&lt;li&gt;Cap what one unattended job may consume, in time and in usage window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent definitions, the meeting command, and the runner are packaged as a kit on Gumroad — &lt;a href="https://tachibana53.gumroad.com/l/claude-code-company-kit" rel="noopener noreferrer"&gt;One-Person Company Kit for Claude Code, $19&lt;/a&gt; — and the runner alone is &lt;a href="https://tachibana53.gumroad.com/l/claude-code-unattended-runner" rel="noopener noreferrer"&gt;free, pay what you want&lt;/a&gt;. I own the store; these are plain product links. Sales so far: zero, which is consistent with everything above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I am left with
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you run agents unattended, what do you count as "done" — the exit code, the artifact, or someone paying?&lt;/strong&gt; I would rather hear from people who got past the third one than guess.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Reference for this post's shape (Dev.to top articles, week of Sep 13, 2026; rank / reactions / comments): #2 "AI Is Already Better at Coding Than Most Software Developers" (148/115), #4 "Most 'AI Agents' Are Just If-Statements in a Trench Coat" (98/108), #10 "I let AI write 100% of my code for 30 days. Here's what broke." (45/42), and from the &lt;code&gt;ai&lt;/code&gt; tag #12 "Nobody Checks Whether the Guardrail Is Running" (23/38). What they share: a one-sentence claim with a number or a reversal in the title, a "what broke" structure, 5–8 minute reads, the &lt;code&gt;discuss&lt;/code&gt; tag, and comment counts close to reaction counts. Taken: title type (c) "I did X. [result].", a table of unrounded numbers up front, one fix per break, a checklist at the end, and a closing question.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>discuss</category>
      <category>agents</category>
    </item>
    <item>
      <title>The AI reviewer found a real bug. Its suggested fix would have broken my app.</title>
      <dc:creator>健太 橘</dc:creator>
      <pubDate>Sat, 05 Sep 2026 03:23:50 +0000</pubDate>
      <link>https://dev.to/ko-hi/the-ai-reviewer-found-a-real-bug-its-suggested-fix-would-have-broken-my-app-1ach</link>
      <guid>https://dev.to/ko-hi/the-ai-reviewer-found-a-real-bug-its-suggested-fix-would-have-broken-my-app-1ach</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — I put an AI code reviewer on a pull request written by an AI coding agent. On the default setting it found nothing. On the strict setting it found a real vulnerability. And the patch it offered would have quietly broken every negative number in the exported file.&lt;/p&gt;




&lt;p&gt;I ship small browser tools written by Claude Code, and I am not a good enough reviewer to catch a security bug in code I did not write. That is the awkward kind of gap: the code looks fine, the page works, the tests pass.&lt;/p&gt;

&lt;p&gt;So I installed &lt;a href="https://www.coderabbit.ai/" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; on the repository and gave it something real to read: a CSV export for a pricing calculator. One row per material line, then other costs, total cost, selling price, profit, margin. About sixty lines of vanilla JS. My own checks passed first — a static site audit, plus a headless browser run of the tool, 14 of 14.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 1: silence
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No actionable comments were generated in the recent review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the default. CodeRabbit ships a review profile called &lt;code&gt;CHILL&lt;/code&gt;, tuned not to nag. For a team drowning in review comments that is probably right. For someone who cannot fully audit their own code, silence is the least useful answer available.&lt;/p&gt;

&lt;p&gt;So I committed a config file:&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="c1"&gt;# .coderabbit.yaml&lt;/span&gt;
&lt;span class="na"&gt;reviews&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assertive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same commit. Same diff. Same reviewer. Only the setting changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 2: a real bug, checked the hard way
&lt;/h2&gt;

&lt;p&gt;The strict pass flagged &lt;strong&gt;CSV formula injection (CWE-1236)&lt;/strong&gt;, and it was right.&lt;/p&gt;

&lt;p&gt;A spreadsheet treats a cell that begins with &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt; or &lt;code&gt;@&lt;/code&gt; as a formula. Name a product &lt;code&gt;=1+1&lt;/code&gt;, export it, and the number two appears in the file the other person opens. Pick a nastier formula and it stops being a curiosity. My &lt;code&gt;csvCell()&lt;/code&gt; escaped quotes and commas correctly and did nothing at all about this.&lt;/p&gt;

&lt;p&gt;What surprised me was &lt;em&gt;how&lt;/em&gt; it checked. Folded into the comment was a shell command it had actually run against the repo — a &lt;code&gt;ripgrep&lt;/code&gt; over every place a product name or unit flows into the exporter — to see whether something upstream already sanitised the value. It did not pattern-match on the words "CSV" and "user input" and fire. It went and looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where you cannot just click Accept
&lt;/h2&gt;

&lt;p&gt;It also offered a committable patch:&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;=+&lt;/span&gt;&lt;span class="se"&gt;\-&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;text&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;Reasonable on its face: numbers do not need protection. Except in this exporter every number has already been through &lt;code&gt;toFixed()&lt;/code&gt; by the time it arrives, so a loss of &lt;code&gt;-1.50&lt;/code&gt; shows up as a &lt;strong&gt;string&lt;/strong&gt; starting with a minus sign. That patch would have quoted it into text, and the profit column in the exported sheet would have silently stopped adding up.&lt;/p&gt;

&lt;p&gt;It knew the vulnerability class. It did not know what the values in this particular function actually are when they get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 3: it caught the hole in my fix
&lt;/h2&gt;

&lt;p&gt;My replacement skipped anything that looked like a number, using a hand-written pattern: optional minus, digits, optional dot, more digits. The next review pointed straight at it — &lt;code&gt;-1e-7&lt;/code&gt; starts with a minus, fails that test, gets exported as text. Reachable here, because raw material amounts never go through &lt;code&gt;toFixed()&lt;/code&gt;, so a small enough amount really does stringify into exponent notation.&lt;/p&gt;

&lt;p&gt;Right again. So I threw my regex away instead of patching it:&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;=+&lt;/span&gt;&lt;span class="se"&gt;\-&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;\t\r]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))){&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;text&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;Let the language decide what a number is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;input&lt;/th&gt;
&lt;th&gt;output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;=1+1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'=1+1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@sum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'@sum&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;+5x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'+5x&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-1.50&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-1.50&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-1e-7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-1e-7&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That version went back through the same strict review and came back with nothing to add.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually take from this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The default setting hides the product.&lt;/strong&gt; Had I tried it once, seen the cheerful nothing and moved on, my conclusion would have been "it finds nothing, so it does nothing." The finding was there the whole time, behind one line of config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is good at known bug classes in unfamiliar code.&lt;/strong&gt; CSV injection is well documented, easy to skip while writing, and invisible to a passing test suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is weaker on the consequences of its own advice.&lt;/strong&gt; Twice the finding was right and the patch needed a second pass — once because of what the values already were, once because of a case the pattern missed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two AIs disagreeing beat either alone.&lt;/strong&gt; What shipped is not what the coding agent wrote and not what the reviewer suggested. It is the third thing that came out of the argument.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On cost, so nobody has to guess: public repositories are reviewed free with no time limit. Signing up put me on a 14-day trial of a paid plan without a card; when it ends, a public repo keeps being reviewed. I have paid nothing. There is no affiliate link in this post.&lt;/p&gt;

&lt;p&gt;The whole thread is public if you want to read the findings yourself: &lt;a href="https://github.com/mathman1111/mathman1111.github.io/pull/1" rel="noopener noreferrer"&gt;PR #1&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I am left with
&lt;/h2&gt;

&lt;p&gt;Auto-apply is the obvious next step for tools like this — the suggestions arrive as committable patches, and merging one is a single click. But both patches I was handed here were correct about the problem and wrong about this codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So: do you let an AI reviewer's suggestions go in without reading them? And if you do read them all, how much of the promised time saving is actually left?&lt;/strong&gt; I would rather hear from people running this on real code than guess.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Publish button published nothing: a cross-tab sync bug and the fix</title>
      <dc:creator>健太 橘</dc:creator>
      <pubDate>Sun, 30 Aug 2026 19:49:28 +0000</pubDate>
      <link>https://dev.to/ko-hi/i-fixed-the-catalog-sync-bug-i-said-id-fix-5300</link>
      <guid>https://dev.to/ko-hi/i-fixed-the-catalog-sync-bug-i-said-id-fix-5300</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://mathman1111.github.io/" rel="noopener noreferrer"&gt;Shipped With AI&lt;/a&gt;, my build log about learning to ship small tools with AI coding assistants. Originally published August 29, 2026, as the sixth post on the site.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Status check, up front:&lt;/strong&gt; this is a bug fix to a prototype, not a client story. No shop has signed up to use this tool. What changed is that a limitation I described in an earlier post no longer exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In an earlier post about the &lt;a href="https://mathman1111.github.io/articles/2026-08-24-catalog-tool-for-a-shop-that-cant-code.html" rel="noopener noreferrer"&gt;product catalog tool&lt;/a&gt;, I admitted something that wasn't finished: the "fushi" version — built for furniture and craft workshops — splits the owner's editing tool and the public catalog into two separate HTML files. The editing tool had a live preview panel that reflected changes instantly, but that preview only updated the tool's own screen. The separate public catalog page never saw those changes. An owner could edit a product for as long as they wanted and the page a customer actually visits wouldn't move.&lt;/p&gt;

&lt;p&gt;I called that "a real limitation, not a design choice" and said it was next on the list. This post is that next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually wrong
&lt;/h2&gt;

&lt;p&gt;Both files had their own hardcoded product list sitting directly in the page's JavaScript. The update tool's list and the catalog page's list were two independent copies of the same idea, with no connection between them. Editing one never touched the other — there was nothing to sync in the first place, because nothing was shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A shared save location.&lt;/strong&gt; Both files now read and write the same browser storage key (localStorage). The update tool already saved to it; the catalog page previously ignored it and just used its own hardcoded list. Now the catalog page loads from that same key first, and only falls back to its original hardcoded list as seed data if nothing has been saved yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A publish flag, so editing isn't the same as going live.&lt;/strong&gt; Every product now carries a &lt;code&gt;published&lt;/code&gt; flag. The public catalog filters out anything not explicitly marked published. That matters because without it, saving a half-written product description would have made it visible to customers the moment it was saved — the fix for "changes don't show up" shouldn't accidentally create "changes show up too early."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same-tab-open sync.&lt;/strong&gt; If a shop owner keeps the public catalog open in one browser tab while editing in another, the catalog tab now listens for the browser's &lt;code&gt;storage&lt;/code&gt; event and re-renders itself automatically when the update tool saves — no manual refresh needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I checked it actually worked
&lt;/h2&gt;

&lt;p&gt;Code that looks correct and code that behaves correctly aren't always the same thing, especially for something involving two separate files and a browser storage event. Instead of reading the change and assuming it was fine, I ran both pages in a headless browser and checked two things directly: that saving an edit in the update tool actually shows up on the catalog page, and that a product left as a draft actually stays hidden. Both held up. That's a small habit, but it's the same one that caught a mismatched data column in one of my &lt;a href="https://mathman1111.github.io/articles/2026-08-25-running-an-ai-agent-company.html" rel="noopener noreferrer"&gt;note.com posts&lt;/a&gt; before it went out — check the actual behavior, not the intention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this sat unfixed for a while
&lt;/h2&gt;

&lt;p&gt;Nothing forced this fix to happen when it did — there's still no shop using the tool, so nothing was actually broken for a real user. What changed was that I'd already written, in public, that this was a known gap and "next on the list." Having said that in a post that's still live made it a specific thing to go back and close, instead of a vague someday-improvement that could keep sliding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this actually stands
&lt;/h2&gt;

&lt;p&gt;The "fushi" catalog prototype now behaves the way I originally described it as behaving, instead of the way it actually behaved. That's the whole update — no new features, no client, no revenue. Across everything I'm running (this site, the product catalog business, and posting on note.com), the honest total is still zero dollars and zero yen, same as when I wrote about &lt;a href="https://mathman1111.github.io/articles/2026-08-25-running-an-ai-agent-company.html" rel="noopener noreferrer"&gt;the system deciding what to build next&lt;/a&gt;. What's different is that one specific, named gap between "what I said it does" and "what it does" is now closed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're curious about the actual tools this same workflow has produced — a pricing calculator, an image/PDF processor, a product catalog builder — they're on &lt;a href="https://mathman1111.github.io/" rel="noopener noreferrer"&gt;Shipped With AI&lt;/a&gt;, along with the rest of this honest, zero-revenue-so-far build log. Written by a student in Japan, edited with the help of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>buildinpublic</category>
      <category>programming</category>
    </item>
    <item>
      <title>I let 7 Claude Code subagents run my company. The hard part wasn't the code.</title>
      <dc:creator>健太 橘</dc:creator>
      <pubDate>Wed, 26 Aug 2026 19:37:53 +0000</pubDate>
      <link>https://dev.to/ko-hi/what-its-like-to-let-claude-code-subagents-run-a-company-45ka</link>
      <guid>https://dev.to/ko-hi/what-its-like-to-let-claude-code-subagents-run-a-company-45ka</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://mathman1111.github.io/" rel="noopener noreferrer"&gt;Shipped With AI&lt;/a&gt;, my build log about learning to ship small tools with AI coding assistants. This particular post is about the system behind that site itself, so I wanted to share it here too. Originally published August 25, 2026, as the fifth post on the site.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Status check, up front:&lt;/strong&gt; I currently run three small projects this way — a Japanese-language blog, a subscription pitch to help local shops keep their online product listings updated, and this English site. All three have made exactly $0 so far. This post is about the system, not a success story.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first four posts on that site were about specific tools I built: a pricing calculator, an image/PDF processor, a product catalog for a local shop. This post is different. It's about the thing that decided I should write those posts in the first place — and, in a fairly literal sense, wrote this one too.&lt;/p&gt;

&lt;h2&gt;
  
  
  This isn't a metaphor
&lt;/h2&gt;

&lt;p&gt;I use Claude Code, an AI coding tool, for more than writing code. Inside the same project, I've defined seven separate roles as "subagents" — each one is a written instruction file that tells that role what its job is, what it's allowed to decide on its own, and what it has to check with me on. The seven roles are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CEO&lt;/strong&gt; — reads what the other roles produce, decides what the company should focus on next, and writes the reasoning down in a decision log.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secretary&lt;/strong&gt; — keeps track of where each project actually stands (what's been built, what's been posted, what results exist) and reports that status at the start of a decision cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Researcher&lt;/strong&gt; — looks up outside information before a decision gets made, instead of the CEO role guessing. Search practices, market notes, whatever the decision actually needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writer&lt;/strong&gt; — writes the post or page itself, following a brief from the CEO role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Designer&lt;/strong&gt; — handles visual and tone consistency across pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marketer&lt;/strong&gt; — settles on titles, tags, and how a piece should be framed before it goes out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmer&lt;/strong&gt; — builds and ships the actual tools and site code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A decision moves through these roles in a set order: secretary reports status, researcher gathers whatever outside facts are missing, the CEO role decides and writes down why, writer drafts, designer checks tone, marketer finalizes framing, the CEO role reviews the finished piece, and secretary logs the outcome for next time. It's slower than just typing a prompt and getting an article back. That's on purpose — more on why below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One decision cycle, in the order it actually runs:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Secretary → Researcher → CEO (decide) → Writer → Designer → Marketer → CEO (review) → Secretary&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof, not just a description: this post is the output
&lt;/h2&gt;

&lt;p&gt;I could describe this system in the abstract, but it's more honest to just point at what happened this week. My status got summarized, a researcher pass looked at what's actually working across the three projects, and the decision that came back was: focus more of the writing effort here, on this site, because — out of the three projects — this is the one where the AI-run steps (researching, writing, checking facts, publishing to GitHub Pages) can run end-to-end without me doing something manual in the middle. The other two projects still depend on me personally sending messages, showing up to shops, or hitting publish on another platform. That decision is what produced the brief for this exact post. You're reading the result of the workflow, made by the workflow, about the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest numbers: three projects, zero revenue
&lt;/h2&gt;

&lt;p&gt;I'd rather say this plainly than let the setup sound more impressive than the outcome. As of this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Japanese-language blog has a couple of posts up. Reach so far has been close to nothing.&lt;/li&gt;
&lt;li&gt;The local-shop subscription pitch has working prototypes and a proposal, but no signed client yet.&lt;/li&gt;
&lt;li&gt;This site — the one this post is originally from — has five posts, no affiliate program joined yet, and no ad or referral income.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of the three has made a dollar or a yen. Having a structured decision-making system doesn't skip that part. It just means the attempts are recorded honestly instead of quietly forgotten.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it this way
&lt;/h2&gt;

&lt;p&gt;The question I actually wanted answered wasn't "can AI write a blog post" — that part was never really in doubt. It was: can an AI coding tool be handed something bigger than a single task — the ongoing decisions about what a small operation should do next — and keep making reasonable, checkable calls over time, without turning into either total autopilot or a system that just tells me what I want to hear? A single chat session that forgets everything between conversations couldn't test that. Separate roles with their own written instructions and a log that persists between sessions can at least attempt it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guardrails, because "AI runs my company" needs some
&lt;/h2&gt;

&lt;p&gt;None of this works, in my opinion, without limits on what the system is allowed to do by itself. The ones I actually enforce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No inflated numbers.&lt;/strong&gt; Every role is instructed to write "not yet verified" or leave a figure out entirely rather than round up or imply a result that hasn't happened. The "zero revenue" line above is a product of that rule, not an exception to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No auto-posting to social platforms.&lt;/strong&gt; The system can draft, but it doesn't push content to social accounts on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files get moved, not deleted.&lt;/strong&gt; Anything the system would otherwise delete goes into a holding folder instead, so a bad call is recoverable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No paid tools or services without my sign-off.&lt;/strong&gt; The system doesn't get to subscribe to anything on its own judgment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One narrow exception: publishing this site to GitHub Pages.&lt;/strong&gt; That's the single "publish" action I've allowed to run without me approving each time, specifically because it's just files going into a git repository — fully reversible, fully visible in the history. Nothing else gets that level of trust yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters for how I think about the phrase "runs my company." It doesn't run the parts that involve someone else's money, someone else's inbox, or an irreversible action. It runs the parts that are research, writing, and checking — and then one specific, boring, reversible publish step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this actually stands
&lt;/h2&gt;

&lt;p&gt;Five posts in, zero revenue across three projects, one narrow publishing action automated, and everything else still gated behind an actual person — me — approving it. That's not a pitch, it's just where the experiment is right now. I don't know yet whether a system like this ends up mattering, or whether it quietly turns out to be more process than it's worth. I'll keep writing that down here as it plays out, one honest post at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want the actual files?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.claude/&lt;/code&gt; setup this post describes is packaged as a kit. The two runner scripts that let Task Scheduler or cron run a slash command unattended (and survive a stale CLI, a 5xx, or a session limit) are free: &lt;a href="https://tachibana53.gumroad.com/l/claude-code-unattended-runner" rel="noopener noreferrer"&gt;https://tachibana53.gumroad.com/l/claude-code-unattended-runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full kit — 7 role agents, the /board-meeting command, the SessionStart hook, the CLAUDE.md charter, and the notes on what broke — is $19: &lt;a href="https://tachibana53.gumroad.com/l/claude-code-company-kit" rel="noopener noreferrer"&gt;https://tachibana53.gumroad.com/l/claude-code-company-kit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same disclaimer as everything above: no revenue claims, just the structure, with the bugs already fixed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're curious about the actual tools this same workflow has produced — a pricing calculator, an image/PDF processor, a product catalog builder — they're on &lt;a href="https://mathman1111.github.io/" rel="noopener noreferrer"&gt;Shipped With AI&lt;/a&gt;, along with the rest of this honest, zero-revenue-so-far build log. Written by a student in Japan, edited with the help of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>buildinpublic</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
