← All articles
Jun 12, 20263 min read

My GitHub Commits Write My X Posts: Claude + GitHub Actions Build-in-Public Automation

  • automation
  • GitHub Actions
  • Claude
  • X
  • build in public
  • LLM
My GitHub Commits Write My X Posts: Claude + GitHub Actions Build-in-Public Automation

Building in public has a tax: after a day of shipping, writing the post about it is the last thing you want to do. So I automated mine — not with canned templates, but with a pipeline that reads what I actually did and writes the way I actually write. It's been posting to @sshahzaiib twice a day, and the interesting parts are all in the guardrails.

The pipeline

A GitHub Actions workflow fires at 09:00 and 17:00 UTC (plus a workflow_dispatch with dry_run defaulting to true — the only sane default for anything that posts publicly):

  1. Fetch reality. A Python script pulls my repos via the GitHub API with a fine-grained PAT and collects my commits from the last two days into activity.json — merge commits excluded, authorship matched. The PAT matters: the default Actions token only sees the current repo, and most of my work happens in private ones.
  2. Draft. anthropics/claude-code-action@v1 (authenticated with a Claude Max OAuth token, not API credits) reads activity.json plus two documents — prompt.md (the rules) and style.md (the voice) — and writes post.txt.
  3. Gate. If the draft is missing or says SKIP, the workflow stops. The LLM has an explicit "nothing worth saying" exit — which is most of why the output stays credible.
  4. Post. A script splits threads on --- (max 3 tweets), enforces 280 chars each, and posts via the X API v2 with OAuth 1.0a.

Memory, or the bot repeats itself

LLMs run statelessly, and a stateless poster converges on the same three openers within a week. The fix is history.json: every published post is appended (rolling window of 15) and committed back to the repo by the workflow itself, then fed into the next prompt with rules — don't reuse opening lines or sentence structures; don't re-post about a repo without new progress; rotate between six post formats (A–F) and never repeat the last two; the "connect" format at most weekly.

Two bugs from this loop earned their scars:

  • The push failed silently. Runners don't persist credentials for pushing; the fix is pushing to an explicitly authenticated URL: https://x-access-token:${GH_TOKEN}@github.com/....git.
  • The bot discovered itself. The history commit is a commit — so the next run found "activity" in the automation repo and tried to post about its own posting. EXCLUDE_REPOS="cowork-automations" in the fetcher. Every self-modifying automation needs the don't-eat-your-own-tail filter; you just don't know it until it eats one.

The economics nobody mentions

X API pricing has a quirk: a post containing a URL costs roughly $0.20 versus ~$0.015 for plain text — over 10× more. So prompt.md simply instructs: no links. The constraint turned out to be good for the content — link-free posts read as thoughts rather than promotions, and the engagement matches. A pricing constraint accidentally enforced a writing principle.

Where the rules drifted

The system's evolution is a small case study in tending automated voice. It started posting every other day, skipping when there was no activity; cadence moved to twice daily, and the skip-on-quiet rule became a fallback — on no-commit days it writes an engagement post (a question, a hot take, behind-the-scenes) about North instead of going silent. Thread support arrived when single posts got cramped. Each change was a one-line edit to a markdown file, which is the underrated property of prompt-as-config: the editorial policy is version-controlled, reviewable, and diffable like everything else.

Does it feel like cheating?

The posts are generated; the activity is real — every one is grounded in commits I actually pushed that day, in a voice document I wrote, through rules I tuned post by post. It's closer to having an editor than a ghostwriter. And it's recursive in a way I enjoy: the first thing the system ever posted about was me building the system — and some of those posts became the seeds for articles on this blog.

WRITTEN BY

Shahzaib Muhammad Akram

Senior Frontend EngineerCyberjaya, Malaysia