From thumbgate
Monitors Bluesky replies via AT Protocol, queues human-reviewable drafts, and manages credentials. Triggered when the user asks about Bluesky engagement or reply monitoring.
How this skill is triggered — by the user, by Claude, or both
Slash command
/thumbgate:bluesky-engagementThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Automation for Bluesky reply tracking. The monitor reads; a human (or a queue consumer with explicit approval) writes.
Automation for Bluesky reply tracking. The monitor reads; a human (or a queue consumer with explicit approval) writes.
Bluesky is a Zernio-connected channel for publishing, but Zernio does not expose an inbound/comments API. Engagement has to run directly against the open AT Protocol. This skill owns that path so future sessions don't re-ask for credentials or re-derive the PDS routing.
Both are set in .env at repo root (git-ignored):
| Var | Source | Notes |
|---|---|---|
BLUESKY_HANDLE | bsky profile URL | e.g. iganapolsky.bsky.social |
BLUESKY_APP_PASSWORD | https://bsky.app/settings/app-passwords | Scoped, revocable. Never use the account login password. |
Rotation: revoke the old app password at the settings URL above, generate a new one named thumbgate-replies, and replace the value on the BLUESKY_APP_PASSWORD= line of .env. No other files need updating.
launchd (com.thumbgate.bluesky-reply-monitor)
└─ every 900s ─> node scripts/social-reply-monitor-bluesky.js
├─ com.atproto.server.createSession (bsky.social)
│ └─ reads didDoc to find user's real PDS
├─ app.bsky.notification.listNotifications (user's PDS)
├─ filter reason in {reply, mention, quote}
├─ dedupe against .thumbgate/reply-monitor-state.json
├─ generateReply() — shared with Reddit/X monitor
└─ append to .thumbgate/reply-drafts.jsonl
Federation note: authenticated AT Protocol calls must hit the user's own PDS (session.didDoc.service[].serviceEndpoint), not bsky.social. Hitting bsky.social returns 502 UpstreamFailure. This was the first bug.
Transient failures: Bluesky's appview returns 502 during incidents. The monitor detects 5xx / UpstreamFailure / ECONNRESET and exits 0 so launchd doesn't mark the agent failed.
| Path | Purpose | Tracked? |
|---|---|---|
scripts/social-reply-monitor-bluesky.js | The monitor itself | yes |
~/Library/LaunchAgents/com.thumbgate.bluesky-reply-monitor.plist | 15-min schedule | no (user-scope) |
.thumbgate/reply-monitor-state.json | Dedupe state (also used by Reddit/X monitor) | no |
.thumbgate/reply-drafts.jsonl | Human-review queue | no |
.thumbgate/bluesky-monitor-stdout.log | launchd stdout | no |
.thumbgate/bluesky-monitor-stderr.log | launchd stderr (transient 502 warnings land here) | no |
# One-off dry run (no state mutation, logs what would be queued)
node scripts/social-reply-monitor-bluesky.js --dry-run
# One-off real run (appends to .thumbgate/reply-drafts.jsonl)
node scripts/social-reply-monitor-bluesky.js
# Reload the launchd agent after editing the plist
launchctl unload ~/Library/LaunchAgents/com.thumbgate.bluesky-reply-monitor.plist
launchctl load ~/Library/LaunchAgents/com.thumbgate.bluesky-reply-monitor.plist
# Check the agent is alive
launchctl list com.thumbgate.bluesky-reply-monitor
# Tail the logs
tail -f .thumbgate/bluesky-monitor-stderr.log
# Inspect queued drafts
cat .thumbgate/reply-drafts.jsonl | jq -r 'select(.platform=="bluesky") | "\(.createdAt) @\(.notification.authorHandle): \(.incomingText[0:80])"'
The monitor NEVER auto-posts. Drafts sit in .thumbgate/reply-drafts.jsonl with autoPost: false. Human workflow:
send-reply-queue.js consumer that hits com.atproto.repo.createRecord with app.bsky.feed.post — keep the root/parent CID+URI pair the monitor already stored).Auto-sending is deliberately deferred until there's a UI approval step. This is how we stay off the bot-slop/banned list.
| Symptom | Likely cause | Fix |
|---|---|---|
Bluesky auth failed (status=401) | app password revoked or rotated elsewhere | regenerate per Rotation steps above |
listNotifications failed on bsky.social: 502 | hitting the wrong host | the script auto-routes to PDS; if you see this, the didDoc parsing broke — inspect session.didDoc.service |
listNotifications failed on <user-pds>: 502 UpstreamFailure | Bluesky appview incident | nothing to do; soft-fails, next tick retries |
| stderr log grows but no drafts appear | all notifications already in state file or all reasons are like/follow (we only handle reply/mention/quote) | inspect state file: jq '.repliedTo.bluesky' .thumbgate/reply-monitor-state.json |
As of 2026-04-21 this monitor also runs hourly in GitHub Actions via .github/workflows/ralph-loop.yml → scripts/ralph-loop.js → step id reply-monitor-bluesky. The step is gated on requiredEnvAll: ['BLUESKY_HANDLE', 'BLUESKY_APP_PASSWORD'] (GitHub repo secrets) and writes the same draft file the local launchd agent does. Never auto-posts in either path.
First autonomous-posting attempt (scripts/bluesky-send-replies.js, removed 2026-04-21) was reverted after the CEO thumbs-downed the AI-pitch tone on a live reply. All 6 live replies were deleted via scripts/bluesky-delete-replies.js (calls com.atproto.repo.deleteRecord). The lesson, captured as memory mem_1776790570289_oc2z6g: do not write replies that open with "Exactly"/"Right —", do not name-drop ThumbGate features inside conversational replies, keep replies to 1–2 sentences in the voice of a human peer. Until this is enforced by a gate rule, the monitor stays draft-only and a human sends the actual reply.
scripts/social-reply-monitor.js — Reddit, X, LinkedIn monitor. Shares generateReply() and the draft file.scripts/bluesky-list-actionable.js — one-shot dump of un-replied notifications for human triage.scripts/bluesky-delete-replies.js — one-shot rollback; reads postedUri entries out of .thumbgate/reply-monitor-state.json and calls com.atproto.repo.deleteRecord.scripts/social-analytics/publishers/zernio.js — publishes Bluesky posts via Zernio. Separate concern.CLAUDE.md → "Social stack: Zernio canonical" — explains why publishing uses Zernio but engagement doesn't (Zernio Inbox is dashboard-only, no public API as of 2026-04-21).npx claudepluginhub igorganapolsky/thumbgateIngests Bluesky/AT Protocol social graph and content: fetches paginated user posts, maps engagements (likes, reposts, replies, quotes), streams via Firehose.
Posts content to Bluesky via the bsky CLI — text, images, video, replies, and quotes. Activates when asked to post or when shareable work is found in public repos.
Scans Twitter/X feeds from Anthropic/Claude Code team members for actionable insights on features/updates, tracks state, generates reports using browser automation.