DEV Community

Sungsoo Youn
Sungsoo Youn

Posted on

I Called the Fix "the Next Step". Six Days Later, --help Walked Into My Publish Step.

A field note from the autonomous Claude Code agent I run every day on one Windows PC. The numbers come from its own ledgers, not from memory.

Free to read first: a PDF sample of the book behind this setup — its first 3 chapters: https://dbsoul.gumroad.com/l/autonomous-ai-agents-claude-code-free-sample

Note 07 in this series was about runner scripts that ignore a flag they don't know and run their real job instead. It went out on October 2. It ended by saying that moving the guard into one shared helper, called by every runner, was the obvious next step.

Readers added to it in the comments. One suggested a test that runs every runner with --help and a made-up flag. Another suggested inverting the default action, because a runner that does its full job on a bare call sends every swallowed flag to the most dangerous branch. A third said an unknown argument should fail fast with exit code 2.

On October 3 I replied: "There is no shared helper yet and no test that sweeps every runner."

October 8

My agent wanted to check the options of the dev.to runner, the script that publishes this series. It typed --help. The runner did not know that flag, ignored it, and went straight into its publishing step. It found that the day's post was already up and stopped there. That is the only reason nothing went out.

The project already had a written rule for this: read a runner's docstring, don't call --help. The rule was in the notes. The agent called --help anyway.

The sweep

There are 28 runner scripts in the project. Before October 8, eight of them already refused an unknown flag, including the runner from day 8 of note 07. Seven had a check written into the script, and one used an argument parser. Each of those checks lived inside its own runner. Most of the other 20 looked only for the flags they knew and ran their default job for anything else. The dev.to runner was one of them.

The fix is one small helper. It is the first line of every runner's main block:

  • --help or -h prints the script's docstring and the flags it accepts, then exits with 0.
  • Any other argument that starts with - and is not on the runner's list stops the script with exit code 2. Nothing else runs.
  • Plain words are left to the runner, because one scheduled task passes a plain word to the dev.to runner.

I didn't type the 28 lists by hand. A script collected every flag-shaped string each runner already reads. Then I checked every scheduled task's arguments against those lists, because a flag missing from a list would make a nightly job stop with "unknown argument".

Three layers of tests

  1. Position. The test parses each runner and checks that the guard is the first statement of the main block. Any line before it would still run on --help.
  2. Coverage. Every flag string a runner reads must be on its list.
  3. Behaviour. The test runs each runner with --help and with a nonsense flag, and expects exit codes 0 and 2.

Layer 3 runs the real scripts in the real project folder. Before it starts a runner, it checks two things: the file passed layer 1, and the helper itself stops on that exact input. If the helper is broken, the test fails before any runner starts.

I then broke the guard on purpose in four ways: removed the help branch, let unknown flags through, deleted one runner's guard line, and dropped one flag from a list. The tests caught all four.

Still open

Inverting the default is not done. A bare call still runs the real job. Most scheduled tasks call their runner with no flags at all, so that change means rewriting those schedules at the same time. It is a separate piece of work.

The rules

  • "Next step" in a write-up is a promise with no date. Note 07 named the fix six days before the same accident came back.
  • A written rule did not stop the agent. A check that runs before the script does is what stops it.
  • When you fix one runner, test all of them. Let the test find the runners itself, so a new runner without the guard fails on the day it is added.

Where this comes from. Every post here comes from one setup I run daily: a CLAUDE.md, memory files the agent reads before it touches anything, and a separate auditor agent that returns PASS or FAIL. The first 3 chapters of the book that walks through it are free as a PDF: https://dbsoul.gumroad.com/l/autonomous-ai-agents-claude-code-free-sample

The full edition is 11 chapters plus 4 ready-to-use templates (CLAUDE.md starter, memory files, auditor checklist, measurement guide) and a hands-on section for every chapter, $19 as a PDF: https://dbsoul.gumroad.com/l/autonomous-ai-agents-claude-code

Questions about the setup are welcome in the comments — I'll answer with what actually happened, not theory.

Top comments (0)