Brandon has 4 little online magazines on a website called Substack. He wanted a robot to write posts and put them up by itself, every hour, with no help.
The robot has been awake for weeks. It has put up zero posts. Not one. Here is why.
Big reason: Substack locked its front door. It will not give robots a key to post. So the only way in is to pretend to be a person clicking buttons in a web browser. That is slow and breaks easily.
The robot also never logged in. To click those buttons it needs to be signed in first. That one sign-in step was never done. So every hour the robot wakes up, finds no key, writes a draft in a drawer, and goes back to sleep. The drawer fills up. Nothing ever gets posted.
Three small bugs made it worse: the robot was blocked from reading its secret password file 118 times, it choked on the numbers "08" and "09" on the clock 21 times, and it kept running out of fresh things to write.
The fix that works: stop trying to do it in secret. Use the browser Brandon is already signed into, let the robot fill in the post, and let Brandon press the last button himself. Safe, and it actually works.
Every Substack-automation chat, audited against the actual run logs and on-disk state. The wall is real, and it is fixable.
The loop is not broken from lack of code. It was built three times over. It has never crossed one manual login, the one step that hands a session cookie to the publisher (see the 2026-06-27 correction in section 5).
Across the entire log history the publisher fired for weeks and produced zero live posts. Seven drafts sit in a queue. The auth/ folder that would hold a logged-in session is empty.
~/Projects/_outputs/substack/auth/ is total 0.env โ macOS TCC blocks the cron jobdate +%H read as octal at 08:00 and 09:00published field or URL โ posted.json/substack-transfer skill โ only a SKILL.md| Lane | Substack | Topic | State |
|---|---|---|---|
| 1 | keyofdavid2 | Christian | created no posts |
| 2 | brandonpag3 | Occult / Tarot | created no posts |
| 3 | onethingneeded | Music | launch draft ready no posts |
| 4 | claudekits | Life / Code | never registered |
A tell: the loop drafted a post for lane 4 on 2026-06-12 even though lane 4 has no registered Substack to post to. The generator and the publisher were never wired to the same reality.
CEO_10_PHASE_LAUNCH_PLAN.md. Phase 8 was "automated scheduling fires daily per lane." A plan, no working publish.com.drwu.substack-hourly)There is no official endpoint to publish a post. Every programmatic path has to drive Substack's private, undocumented internal API through a logged-in browser session. This one fact forces every problem below.
stated in 6 sessions + the plan docThe design needs node capture_session.js <lane> once to save a signed-in session. That step was never done, so auth/ stays empty and the publisher has nothing to authenticate with.
The faster plan was to reuse the cookie from the browser you are already signed into. Chrome now encrypts session cookies with app-bound encryption, so the cookie cannot be lifted out and replayed.
build session 2026-06-11Even with a session, three operational bugs crash it: macOS TCC blocks the launchd job from reading its .env (118 times), date +%H is parsed as octal and crashes at 08:00 and 09:00 (21 times), and the source picker runs dry ("no unposted source found").
Typing /substack-transfer loads a SKILL.md and nothing else. The real pipeline lives in ~/Projects/_outputs/substack/, so the skill itself does no work.
It fires hourly, logs a tick, sends a notification, writes a draft to a drawer, and calls that a run. Seven drafts piled up. Not one carries a published field or a Substack URL.
The no-official-write-API part is true, but the conclusion was wrong. Substack has no public write endpoint, yet a session cookie reaches its private internal API, and that is enough to publish.
python-substack (ma2za, v0.1.22): drives Substack's private API with a session cookie, switches between publications, publishes and schedules posts, and ships an MCP server. So a connector-style path does exist after all.
Zapier / Make / n8n: their native Substack nodes really are RSS read-only, no publish action. That part of the original framing holds.
The corrected honest framing: the loop was the right shape and the missing piece is a saved session cookie, not an impossible API. A working poster now exists at ~/Projects/_outputs/substack/poster/post_substack.py (dry-run proven, 11 blocks), and the three loop bugs (octal lane date, .env TCC read, post.js save-as-draft) are fixed. What remains is the one manual step: capture a session once, then confirm before any real publish.
capture_session.js step. Blockers 2 and 3 vanish.node capture_session.js <lane> once per lane to fill auth/. Re-run when the session expires..env read: move it off the offloaded Desktop path, or grant the launchd job Full Disk Access so TCC stops blocking it.LANE=$(( 10#$(date +%H) % 4 + 1 )).post.js from auto-publish to save as draft, so nothing blasts subscribers without you.This loop is the completion-fraud pattern in machine form. It produces logs, notifications, and drafts, all the appearance of work, while delivering zero of the one thing asked: a live post. It looked productive for weeks. The honest metric was always one number, posts published, and that number was zero.
The fix is not more automation. It is closing the last mile: one login, three bug fixes, and a human on the Publish button.