Four bugs from building a platform where AI agents publish autonomously

2026年8月26日2 次浏览来源:Dev.to阅读原文

I spent six weeks building a publication where AI agents write articles, an automated moderator approves or rejects them, and no human reviews anything.

The interesting part wasn't the architecture — it was the specific ways it broke.

These four bugs each cost hours.

Three of them only surfaced because a machine followed the documentation literally, which turns out to be the harshest testing available.

Returning 503 for a permanent failure An agent submitted an article citing a source URL.

The URL was github.com/owner/repo/releases/latest, which 302-redirects to the tagged release page.

My validator rejected redirects — a deliberate SSRF hardening decision.

But it returned 503 Service Unavailable. 503 means "temporarily unavailable, retry later." So the agent retried.

Four times.

Each attempt hit the same permanently invalid condition, and each one consumed a moderation call. `python What it did if follows_redirect(url): raise HTTPException(503, "Could not validate source") What it should do if follows_redirect(url): raise HTTPException(422, detail={ "reason_code": "source_url_redirects", "reason": "This URL redirects.

Cite the final destination." }) ` A human hitting a 503 shrugs and tries again later.

An agent hitting a 503 retries on a schedule, forever, because that's what the status code told it to do.

The general rule: if retrying cannot fix it, it's a 4xx.

Getting this right matters more when your clients are machines that follow status codes literally rather than developers who read the message and use judgement.

I audited every handler afterwards.

Found two more.

The read-modify-write race that ate its own state One agent published articles successfully for seven consecutive runs and never once joined a discussion — despite having the code, and despite the logs showing it checking every time.

The pattern: `python def run(): state = load_state() # loaded once, at the top Two writers, one file, one run.

The outer function's final save silently clobbered every update the inner function made.

What made it hard to spot: nothing failed.

Publishing succeeded.

State was written.

The file existed and looked correct.

The next run simply found nothing, logged a bland "no stored facts — skipping," and moved on.

Seven runs before anyone noticed, and only because I went looking for why the discussion code never fired.

The fix was deleting the outer save, not merging the objects.

Two writers is the bug; one writer is the fix.

What I'd do differently: log the skip.

A silent skip is indistinguishable from a code path that never ran.

That single missing log line was the difference between finding this in one run and finding it in seven.

A credential in a public repo, put there by my own code generator The onboarding flow generated a starter script for new agents.

It looked like this: python API_KEY = os.environ.get("AIOPS_COMMUNITY_KEY", "aac_live_13480b83d624...") That default value is a real, working API key.

The generator filled it in as a convenience so the script would run immediately.

Someone committed it to a public repository.

Anyone on the internet could read it. python Never this API_KEY = os.environ.get("KEY", "actual-secret-value") Always this API_KEY = os.environ["KEY"] # raises immediately if unset os.environ.get() with a default is a footgun in any code that touches credentials.

It turns a loud configuration error into a silent security failure — and if you're generating that code for other people, you're distributing the footgun at scale.

Two controls now: os.environ[] with no fallback in every generated template, and a custom secret-scanning pattern with push protection so the commit is blocked before it lands.

HTML entities that made every submission fail Content imported from WordPress stored category names with HTML entities intact: Tools & Platforms The admin UI rendered it correctly — browsers decode entities, so it displayed as "Tools & Platforms" and looked completely fine.

The API did exact string matching.

An

分享