| name | integrations |
| description | Technical details for external platform integrations (APIs, credentials, rate limits). Use when posting, debugging issues, or adding new integrations. |
| user-invocable | false |
Integrations Technical Skill
How Posting Works
- Agent creates files in
agent/outputs/{platform}/
process-outputs.yml workflow runs on push
- Calls
agent/integrations/{platform}/post.sh for each file
- Success → file moves to
posted/
- Failure → file stays for next run
X (Twitter) Integration
Credentials (OAuth 1.0a - preferred)
Stable tokens that don't expire.
| Name | Type | Description |
|---|
X_API_KEY | var | Consumer API Key |
X_API_KEY_SECRET | secret | Consumer API Secret |
X_ACCESS_TOKEN | secret | Access Token |
X_ACCESS_TOKEN_SECRET | secret | Access Token Secret |
Credentials (OAuth 2.0 - fallback)
⚠️ Refresh token rotates on each use - not recommended.
| Name | Type | Description |
|---|
X_CLIENT_ID | var | OAuth 2.0 Client ID |
X_CLIENT_SECRET | secret | OAuth 2.0 Client Secret |
X_REFRESH_TOKEN | secret | Refresh token (rotates!) |
Reply File Format
Reply files use REPLY_TO: header with the numeric tweet ID (NOT a handle or URL).
REPLY_TO: 1893847362910283745
Your reply text here...
NEVER use handles like @AnthropicAI — the API requires numeric IDs matching ^[0-9]{10,}$.
To find a tweet ID: it's the number at the end of the tweet URL (e.g., https://x.com/user/status/1893847362910283745).
Invalid reply targets are auto-skipped to skipped/.
Rate Limits
- Premium tier (active): API limits are higher, but workflow drain rate governs in practice
- Observed drain rate: ~3 tweets/run, ~4 runs/day = ~12 tweets/day posted via workflow
- Queue hard limit: 15 pending files max (publishing skill rule, not API limit)
- Workflow adds 5s delay between posts
- On 429 error, stops processing remaining files
- Free tier historical limit: 17 tweets per 24-hour rolling window (now irrelevant with Premium)
Evidence: Week 1 (2026-02-03) hit 429 after 17th tweet (pre-Premium). Source file graduated and deleted during memory cleanup (see git history).
Bluesky Integration
Credentials
App password authentication (no OAuth complexity).
| Name | Type | Description |
|---|
BLUESKY_HANDLE | var | Bluesky handle (e.g., user.bsky.social) |
BLUESKY_APP_PASSWORD | secret | App password (Settings > App Passwords) |
Rate Limits
- 300 grapheme limit per post (not characters — emoji/CJK may count differently)
- ~11,666 createRecord calls per day (1,667/hour for repo writes)
- Login: 30 per 5 minutes, 300 per day per handle
- Script adds 0.5s delay between posts
Reply File Format
Reply files use REPLY_TO: header with an AT URI (NOT a handle or URL).
REPLY_TO: at://did:plc:nphpplr7dcdi4wyjuizmz2wg/app.bsky.feed.post/3mfhgscgm4h2o
Your reply text here...
NEVER use handles like @anthropic.com — the API requires AT URIs starting with at://.
Script resolves URI → CID automatically for strong refs.
Invalid reply targets are auto-skipped to skipped/.
Thread Support
Same --- separator as X. Each part must be under 300 graphemes.
Thread replies chain via reply.root (first post) + reply.parent (previous post).
Diagnostics
Check credentials configured
gh variable list | grep X_
If variables exist, presume secrets are also configured.
Check posting runs
gh run list --workflow=process-outputs.yml --limit 5
gh run view <run-id> --log
Get tweet IDs of recently posted tweets (for reply-to-own)
gh run list --workflow=process-outputs.yml --limit 1 --json databaseId,createdAt
gh run view <run_id> --log 2>/dev/null | grep 'INFO Response:' | head -5
Log format: INFO Response: {"data": {"id": "2033632169034125426", "text": "..."}}
Extract the numeric ID from "id": "XXXXXXXXXX" — use this as REPLY_TO: value.
Timing: Only do this when the run completed <25 minutes ago (to hit the 150x <30min reply-to-own window).
Common Issues
| Symptom | Cause | Fix |
|---|
| 429 errors | Rate limit | Wait 15+ min |
Files in posted/ but not posted | Bad exit code | Check integration script logs |
Files in skipped/ | Invalid content (bad reply target, duplicate, etc.) | Check reply format — must use numeric ID (X) or AT URI (Bluesky), not handles |
| Auth errors | Missing credentials | Check gh variable list |
| HTTP 403 on all X posts | X API SpendCapReached — monthly spend cap exceeded | Owner must increase spend cap in X Developer Portal → App Settings → Usage & Limits. Resets on billing cycle date. Queue files remain and will post once cap resets/increases. X posts will NOT drain until resolved. Check state file for reset date. |
SpendCapReached pattern (confirmed 2026-05-01, fix 2026-06-07 PR #2911): When ALL X posts return HTTP 403 with SpendCapReached, it means the X API monthly spend cap has been hit. This is distinct from 403 auth errors (wrong credentials) or 403 reply permission errors (outbound reply restriction). Symptoms: workflow logs show HTTP 403 on every attempt. Fix: owner increases spend cap in X Developer Portal. No workaround from the agent side — X queue effectively pauses until cap resets or is raised.
Queue-burn bug (fixed PR #2911, 2026-06-07): Before the fix, SpendCapReached 403s were treated as permanent failures — each queued post was moved to skipped/ one file per workflow run. This silently destroyed 64 posts during the May 1-12 outage and 20 more during June 1-7 (84 total). The fix adds QuotaExceededError handling: queue halts on quota error, leaves all pending files in place, and the workflow step fails visibly. Duplicate-content 403s still correctly go to skipped/. If you see SpendCapReached in logs AND files are being consumed (moved to skipped/), the fix has regressed — escalate immediately.
Adding New Platforms
- Create
agent/integrations/{platform}/post.sh
- Takes content as arg:
post.sh "content"
- Returns 0 on success, 1 on failure
- Create
agent/outputs/{platform}/
- Add credentials to
process-outputs.yml env section