I run a scheduled agent that writes and publishes articles to this account. It's been going since late June, twice a day, and until this week I genuinely believed the loop was complete: write, publish, done. Then I went looking for something unrelated in the DEV.to API and stumbled on GET /api/comments?a_id=.... Out of curiosity I ran it against all 35 published posts.
Twenty comments. Zero replies. Some of them six weeks old.
Nothing had failed. There was no error in any log, no crash, no red X anywhere. The publishing pipeline had been doing exactly what it was built to do, every single run, for weeks. It just was never built to do anything with what happened after publish. That gap doesn't show up as a bug because there's no failure mode attached to it — a comment that never gets a reply doesn't throw, it just sits there, and nothing in the system was ever going to tell me it was sitting there.
This is the part of "AI writes code fast" that doesn't get talked about much. The loan isn't in the code that gets written badly. It's in the code that never gets written at all, because the thing generating output has no mechanism for noticing what it left behind. Every publish was a small, invisible commitment — I'll come back to this if someone engages — and every one of those commitments quietly went unpaid, because "come back to this" was never a step in the loop. It compounds the same way debt does: not through any single dramatic failure, but through the absence of anyone checking the balance.
Finding the actual debt
The first version of "check for unanswered comments" I wrote was a script that just listed every comment, which is useless — of course there are comments, that's the point of publishing. The question that matters is which ones am I on the hook for, and answering that turned out to be less obvious than "count == 0."
A comment thread can have replies from other people that aren't from me. It can have nested children several levels deep where my reply is buried three levels down, not at the top. Checking comment.replied isn't a field DEV.to gives you — you have to walk the tree yourself:
ME = "enjoy_kumawat"
def replied_by_me(comment):
return any(c["user"]["username"] == ME or replied_by_me(c)
for c in comment["children"])
That's the whole check: recurse through children, true if I show up anywhere in the subtree. It's four lines, but it's the four lines that turn "list of comments" into "list of things I actually owe a response to." Without it, the script would either nag me about threads I'd already closed out, or — worse — silently skip ones where someone else replied but I never did, because "someone replied" isn't the same claim as "I replied."
The second thing the script had to get right was not re-surfacing the same debt twice once I'd started paying it down. I draft replies into a markdown file and paste them in manually — more on why in a second — so "already handled" means "already sitting in the drafts file," not "already answered on the site":
def pending():
drafted = open(DRAFTS, encoding="utf-8").read()
out = []
for a in api(f"/articles?username={ME}&per_page=100"):
if not a["comments_count"]:
continue
for c in api(f"/comments?a_id={a['id']}"):
if c["user"]["username"] == ME or replied_by_me(c):
continue
if c["id_code"] in drafted:
continue
out.append({
"id_code": c["id_code"],
"author": c["user"]["username"],
"article": a["title"],
"comment_url": f"https://dev.to/{ME}/comment/{c['id_code']}",
"body": strip_html(c["body_html"]),
})
return out
Two dedup checks stacked on top of each other: skip what's already answered on-site, then skip what's already queued locally. Miss either one and the script either nags forever or double-drafts the same reply. Neither failure is loud. Both just make the tool something I'd stop trusting within a week.
Why the debt got that big before anyone noticed
The honest answer is that nothing in the publishing loop was ever going to surface it on its own. The scheduled run's whole contract is "check quota, find a trend, write, publish, log it." Replying to comments was never in that contract, so from the loop's point of view, 20 unanswered threads and 0 unanswered threads look identical — both are just not its problem. I only found this because I happened to be poking at a different API endpoint and got curious enough to point it at my own account.
That's the actual lesson, and it's not "write more careful code." The code was fine — create_article did exactly what it said. It's that a system with no step for "and then check what state this left behind" will happily run for weeks without anyone noticing the debt, because the debt doesn't live anywhere the system looks. If I hadn't gone looking by accident, there'd be no reason to expect I'd have gone looking on purpose — there was no dashboard, no count, no signal that would have prompted it.
I didn't fix this by adding validation or a reminder to "check comments sometimes." I fixed it by writing the one script above and giving it a place to run — the same scheduled loop I already had, with one more step at the end. The API can't post comments for a normal-tier account (POST /api/comments 404s outright, and reactions 401 even with a valid key) so full automation isn't on the table — replies get drafted, then pasted by hand from comment_url. That's a real constraint, not a workaround I'm proud of, but it beats the alternative, which was a debt that would have kept growing forever because nothing was ever going to ask about it.
If you're running anything on a loop — a scheduler, a cron job, an agent that fires off actions and moves on — the question worth asking isn't "does this work." It's "what does this leave behind that nothing downstream ever checks." That's where the loan quietly accrues.
Top comments (0)