X / Twitter
X / Twitter mentions, allowlisted public replies, thread context, and event modes
The X plugin turns mentions of your bot account into agent conversations and posts the agent's answer as a public reply. Only allowlisted authors can trigger a reply by default. Unknown authors are silently dropped unless you enable guest mode; they receive no pairing prompt.
Each X conversation is a group thread identified by its conversation_id.
The agent receives the triggering mention together with available ancestors,
conversation posts, and quoted posts. The original mention remains the
user-visible message. DMs, original posts, likes, follows, and media uploads are
not supported.
Setup
The plugin is bundled with builds that include extensions/x. To add it to an
OpenClaw 2026.9.8 installation from a local checkout:
openclaw plugins install --link ./extensions/xUse an X confidential OAuth2 application and authorize the bot account with
tweet.read tweet.write users.read offline.access. Keep its client ID, client
secret, and user-context refresh token. The plugin refreshes access tokens with
HTTP Basic client authentication. An optional, separate app-only bearer token
enables the Activity API.
X can rotate the refresh token when issuing an access token. The plugin saves
the latest token in private, worker-backed plugin state (x.oauth) so it
survives restarts. Changing the configured refresh-token seed starts a new
token lineage. This storage does not promise encryption at rest; protect the
Gateway's state directory and its backups as credentials.
Set the bot's numeric user ID and username, then add at least one maintainer's
numeric X user ID to allowFrom:
{
channels: {
x: {
enabled: true,
userId: "123456789",
username: "example_bot",
clientId: "example-x-client-id",
clientSecret: { source: "env", provider: "default", id: "X_CLIENT_SECRET" },
refreshToken: { source: "env", provider: "default", id: "X_REFRESH_TOKEN" },
bearerToken: { source: "env", provider: "default", id: "X_BEARER_TOKEN" },
allowFrom: ["987654321"],
groupPolicy: "allowlist",
dmPolicy: "disabled",
events: { mode: "auto", pollSeconds: 60 },
threadContext: { maxPosts: 50 },
costLimits: { dailyUsd: 100, monthlyUsd: 1000, cycleStartDay: 1 },
},
},
bindings: [{ agentId: "main", match: { channel: "x" } }],
}Replace the example IDs and username. Make the referenced environment variables
available to the Gateway. Omit bearerToken to use polling without the Activity
API. All three secret fields also accept plaintext or supported
SecretRef inputs.
Run openclaw config validate and openclaw channels status. From an
allowlisted account, mention the bot in a post. A successful turn produces a
public reply beneath that post. Normal channel bindings
choose the agent and session; the plugin does not override session scope.
For multiple bots, put account-specific values under
channels.x.accounts.<accountId>. Root fields are shared defaults; the default
account ID is default.
Public work sessions
Set channels.x.accounts.<accountId>.autoPublishWorkSessions: true (or the
shared root default channels.x.autoPublishWorkSessions) to publish fresh,
isolated visible work sessions spawned directly by admitted maintainer mentions.
Guest mentions remain limited to hidden helpers and cannot publish work sessions.
This is off by default. It exposes that child's conversation to anonymous readers
at the same canonical /chat link; it does not change Team collaboration rights.
Only enable it for an agent whose work is intended to be public.
Publication requires a configured app-only bearerToken. Before admission,
the plugin looks up every post included in the supplied thread context using
application-only authentication and requires explicit protected: false
author metadata. Missing, edited, protected, withheld, or unavailable posts and
failed lookups deny automatic publication. A permalink or successful
user-context lookup is not proof of a public audience. Verification reads share
the account's X API budget; insufficient budget denies publication without
bypassing the cost limits.
The permission belongs only to that incoming invocation: it is not stored on
the X conversation, inherited by grandchildren, or accepted in model-authored
spawn arguments. Forks, private/draft sessions, incognito sessions, and existing
sessions cannot be automatically published. Changing the account configuration
or allowlist retires in-flight publication authority. The creation owner commits
the public grant with the child before its first turn, and the spawn receipt
reports publicRead only for the committed grant. Links without that receipt
are labeled as requiring sign-in. This lane requires the live in-process Gateway;
it does not downgrade to a transport that loses the invocation's authority.
Manual publication remains governed by the existing creator/administrator sharing controls. No X identity is promoted to a Team profile or administrator.
Manage the allowlist
Open X replies in the Control UI as an administrator. The page shows the
effective union of config allowFrom entries, users added through the page, and
GitHub-derived users when GitHub verification
is configured.
Add a username to resolve it to a stable numeric X user ID. Stored entries retain
the resolved username, display name, adding operator, and timestamp. Remove a
stored entry from the page; config entries are read-only and must be removed
from config. The From GitHub section is also read-only and shows each X
handle, its source GitHub login, repository permission, and last sync time.
Removing a stored entry does not revoke access granted by another source.
The page also shows this account's estimated X API spend for today and the
current billing cycle against its limits. Select Refresh to update the
read-only totals. The same spend snapshot is included in x.allowlist.list.
A locally linked installation uses the Custom plugin UI setting. Enable Settings → Labs → Custom plugin UI, then use the Control UI served by the Gateway over HTTPS or trusted loopback. Bundled installations do not need that setting.
The Gateway methods x.allowlist.list, x.allowlist.add, and
x.allowlist.remove require operator.admin. Authorize senders by numeric ID,
either 987654321 or x:987654321; handles belong in the add-by-handle UI, not
in allowFrom.
guests.enabled controls admission outside the maintainer allowlist. The legacy
groupPolicy: "open" setting is unnecessary and cannot bypass the guest switch.
groupPolicy: "disabled" turns off all inbound turns. dmPolicy accepts only
disabled.
Verify GitHub collaborators
Set verifiedFromGitHub.repo to grant the verified maintainer tier to people
with write access to a GitHub repository who declare an X account on their
GitHub profile. This is off by default. Derived numeric X user IDs join
config allowFrom and administrator-managed entries; they receive the same
maintainer sessions, tools, and reply behavior.
{
channels: {
x: {
verifiedFromGitHub: {
repo: "openclaw/openclaw",
minPermission: "push",
refreshMinutes: 60,
token: { source: "env", provider: "default", id: "X_GITHUB_TOKEN" },
},
},
},
}These settings inherit from channels.x into each account, with per-field
account overrides. repo uses owner/name format. minPermission accepts
push (write, maintain, or admin), maintain (maintain or admin), or admin.
The refresh interval defaults to 60 minutes and cannot be shorter than 15.
The X plugin uses its configured GitHub token for read-only REST calls to
api.github.com. The existing GitHub tools identity does not expose a shared
authenticated REST client, so configuring tools.github alone does not provide
this credential. Set token to a SecretRef available to the
Gateway. The credential must be able to list all collaborators on the selected
repository, including their permissions, and read public user profiles and
social accounts. For a fine-grained token, grant access to that repository and
Metadata: read; organization policies may also require approval or SSO
authorization. A credential that cannot list collaborators produces a visible
sync error instead of silently treating the repository as empty.
A plugin service syncs on startup and at the configured interval. It paginates
all collaborators, filters by repository permission, and reads each eligible
GitHub profile's twitter_username and Twitter/X social-account links. Multiple
valid declarations are combined and duplicate handles are removed. Bare handles,
@handles, and X/Twitter profile URLs are accepted. It resolves declared handles
to numeric X IDs in batches of up to 100
using the account's existing X API client and budget. Each returned user costs
$0.01. Handle-to-ID caching avoids repeated paid lookups for unchanged
handles. Unresolvable handles are skipped, cached, and shown in status. To retry
an unchanged unresolved declaration, remove it for a sync and then add it back,
or change the declaration. GitHub profile caching uses ETags where available
and lasts for the running service's lifetime.
Derived entries and the handle-resolution cache live in worker-backed plugin keyed state under
x.verified-github. A successful sync replaces the derived set. Losing the
required repository permission or removing the declared handle removes that
entry at the next successful sync. Removals use the existing allowlist
revocation fence, invalidating in-flight authorization. Other allowlist sources
still apply. A failed sync keeps the last good set, marks it stale, and logs
once per failure streak; it does not revoke everyone during a GitHub outage.
Successful paid lookup batches are cached even if a later batch fails, so the
next sync can finish without paying again for those users. Unchanged refreshes
and stale-status updates preserve in-flight authorization.
Channel status includes verifiedFromGitHub with repo, entries,
unresolvedHandles, lastSyncAt, and stale. The From GitHub section on
X replies shows the same source attribution and freshness. Page Refresh
reloads the current snapshot; the plugin service owns synchronization.
Trust model
The GitHub account owner declares the X handle and already has write access to the repository. Mapping that account to the verified tier adds no meaningful power within this trust model. The residual risk is a collaborator declaring an X handle they do not own. Administrators can inspect the source GitHub login for every derived entry on X replies. Revoking the collaborator's repository access or removing the profile declaration removes the derived entry on the next successful sync, subject to the last-good-set behavior during failures.
The host authorizes the resolved numeric X ID, never a handle supplied in a post or by the model. The sender line records the source, for example:
This is from a verified user: @example (Example), X user id 123, GitHub @example-dev with write access to openclaw/openclaw.Enable yourself
- Sign in to the GitHub account that has the required repository permission.
- Open Settings → Public profile.
- Add your X handle to the Twitter username field, or add your
https://x.com/your_handleprofile URL under Social accounts. - Save your profile and wait for the next sync (60 minutes by default).
- Mention the X bot from that X account. An administrator can confirm your mapping in X replies → From GitHub.
Without a declared, resolvable X handle, repository access alone cannot map you to an X account. You can also ask an administrator to add your X account manually. To remove the derived mapping, remove the declaration from your GitHub profile or have your repository permission revoked.
Guest mode
Guest mode lets people outside the maintainer allowlist ask repository questions. It defaults to off. After configuring the front-door agent below, use the Guest mode switch on X replies, or run:
openclaw config set channels.x.guests.enabled true
openclaw config set channels.x.guests.enabled falseThe config watcher reloads the X channel without restarting the Gateway. The UI
uses the administrator-only x.guests.set method and persists the same config.
For an explicitly configured account, the switch writes that account's override;
otherwise it writes channels.x.guests.enabled. Account overrides inherit the
other guest settings from the channel root.
Guests receive the core read, ls, sessions_spawn, sessions_yield, and
subagents tools, further restricted by the agent's normal policy. They can
start hidden helpers of the same agent. Helpers inherit the guest's restricted
tools and repository root; they cannot become visible work sessions or target
another agent. sessions_yield waits for helper completion, and subagents
lists, waits for, or cancels helpers.
Guests cannot edit files, run commands, browse or fetch the web, use memory,
send messages, or inspect unrelated sessions. The optional guests.tools.allow
can narrow the five default tools; an empty array disables all tools.
guests.tools.deny takes precedence. Neither setting can add stronger tools.
Hidden helpers require a host that advertises enforcement of these restrictions.
Older hosts keep the read and ls defaults, even when they report the same
OpenClaw version. The X replies settings page shows upgrade guidance when helper
support is missing. Explicitly selecting only helper tools on an older host
disables all guest tools; it does not restore the default read tools.
Each guest mention gets a separate channel session and the quoted X thread context. It does not reuse a maintainer's conversation history, permission mode, root directory, or selected skills. Maintainers keep their existing sessions, tools, and work-session replies. Guest replies may cite documentation URLs, but OpenClaw never appends a work-session link to a guest reply.
Repository containment
Tool names alone do not confine filesystem reads. Configure the X front-door
agent's cwd and workspace to the OpenClaw clone and enable the core's
workspace-only file guard. The supported guest setup also disables selected
skills and Docker/remote sandbox mode: skill directories and sandbox mounts are
explicit read exceptions in core and can expose files outside that clone.
For example, add these fields to the agent selected by your X binding:
{
workspace: "/srv/openclaw",
cwd: "/srv/openclaw",
skills: [],
sandbox: { mode: "off" },
tools: { fs: { workspaceOnly: true } },
}The effective filesystem setting is
agents.entries.<agentId>.tools.fs.workspaceOnly, falling back to
tools.fs.workspaceOnly. The X plugin refuses guests before thread expansion
when that setting is absent or false, skills are enabled, or sandbox mode is
active. Channel status reports the required correction as
guestModeBlockedReason; maintainer mentions continue normally.
Guest mode also requires a queue mode that cannot steer or interrupt an active turn. Set the channel override before enabling guests:
{
messages: { queue: { byChannel: { x: "followup" } } },
}collect is also supported. Without a channel override, messages.queue.mode
must be followup or collect; the default steer and explicit interrupt
block guest admission before thread expansion. The X replies page shows
the required setting in its existing guest-readiness message. Changing the
queue mode back to either unsafe value blocks subsequent guest mentions.
Core owns path and symlink containment and rejects reads outside the session
root with Path escapes sandbox root. Keep guest channel sessions in their
initial permissions: do not grant full permission, widen their session root,
or attach external skills or skill-library pins through operator controls.
Those operator actions deliberately change core's filesystem authority. Guest
turns have no tools that can make those changes. Put maintainer work in their
normal work sessions when it needs a broader filesystem or skills.
Limits and identity
The default limit is 5 mentions per guest author per UTC day, independently
for each bot account. Set guests.maxMentionsPerAuthorPerDay from 0 to 1000;
0 admits no guests. Over-limit mentions are dropped silently before thread
expansion. Guest thread context defaults to 10 posts, controlled by
guests.threadContextMaxPosts; maintainers retain threadContext.maxPosts.
The UI and channel status expose guests.enabled, admittedToday, and
rateLimitedToday. Admitted counts include reserved turns even when a later
thread lookup or model run fails; retries reuse their reservation.
Usage lives in bounded, worker-backed plugin state (x.guest-usage) for two
days. Capacity exhaustion pauses new guest admissions rather than evicting a
current author's quota. Recent rejected post IDs are retained to avoid counting
normal retries twice; unusually late retries after that bounded history can
increment the rate-limited statistic again. The ingress queue separately
suppresses completed event replay.
Guest turns incur the same X API and model costs as other turns. Thread reads and each reply post are billed normally; a guest citation URL receives X's higher URL-containing reply price. The author quota is not a dollar budget.
Security: the host determines the tier only from X's numeric author_id
against the effective union of configured, administrator-managed, and
GitHub-derived allowlist entries. Handles, display names, post text, and model output never grant
maintainer access. Every agent-facing turn starts with a host-generated sender
line; all thread posts below it are quoted data. Removing a maintainer from the
allowlist makes subsequent mentions guests when guest mode is on.
Event modes
| Mode | Behavior |
|---|---|
auto | Uses the Activity API when a bearer token is configured and subscriptions succeed; otherwise polls mentions. |
stream | Requests Activity API streaming with the app-only bearer token; falls back to polling when no bearer token is configured. |
poll | Polls the mentions endpoint using the user-context token. |
The default is auto. Streaming lists existing subscriptions with the app-only
bearerToken, then creates a missing post.mention.create subscription for the
bot with its OAuth2 user access token (refreshed from refreshToken). Mention
subscriptions require the user to grant tweet.read. The persistent
GET /2/activity/stream connection uses the app-only bearer token, as specified
by X's Activity Stream API.
Streaming reads post.mention.create events from X's data.payload envelope,
checks that the post addresses the bot, and ignores blank keep-alives. Other
event types do not create inbound turns; delivered post.* events still count
toward the budget. It reconnects with backoff after a stalled or disconnected
stream.
Each connection runs a mentions backfill with the user token from the saved
cursor, then repeats it every events.pollSeconds × 4
(minimum 60 seconds, default 240 seconds) while streaming. This safety backfill
recovers mentions missed by the stream. Only completed backfill pages advance
the cursor, so newer stream events cannot hide older missed mentions. Post IDs
deduplicate stream and polling events.
After three consecutive unparseable mention events or malformed lines, the
plugin logs one warning and shows it in the channel status message. Blank
keep-alives and intentionally ignored event types do not count toward this
warning. It includes the event type and top-level keys, without post content.
The next parseable mention event clears the warning.
In auto mode, any subscription setup failure switches to polling. In stream
mode, subscription HTTP 403 switches to polling; other setup errors stop the
event source. A stream HTTP 401 or 403 switches either mode to polling. The
channel status message includes the failed endpoint, HTTP status, and first X
error message when available, with credentials redacted. For example:
X API /2/activity/subscriptions failed (HTTP 400): OauthAccessTokenRequired: OAuth user access token is required for this event type; pollingThis message explains why Activity could not start; polling remains active. Unreadable error bodies still report the HTTP status. Network and token-refresh failures report their client error without provider response details.
Polling defaults to 60 seconds; events.pollSeconds cannot be less than 15.
Each request asks for 10 mentions, X's minimum page size, and follows pagination
when a backlog remains. This keeps each request's reservation small while still
catching up on all available mentions.
Inbound posts are durably queued before the cursor advances. Completed event
IDs are retained for up to 30 days with a limit of 2,000 completed entries per
account, preventing duplicate turns after reconnects and restarts while those
entries remain retained.
Thread context and replies
The plugin renders available thread posts oldest first as @handle (time): text
and marks the triggering mention. It follows reply ancestors, reads the recent
conversation, and includes quoted posts. threadContext.maxPosts defaults to
50; the root and newest posts are kept when the limit is reached. Recent-search
coverage is limited to seven days, and unavailable or deleted posts cannot be
included.
Conversation searches request between 10 and 100 posts, bounded by the remaining context allowance; X requires a minimum of 10. Pagination stops when the context limit is reached. If the budget cannot cover more context, the agent receives the mention and any context already fetched, marked "thread context truncated by budget."
Replies are split into a self-reply chain with at most 280 weighted characters
per post; each URL counts as 23 characters. The last chunk receives
replySignature, whose default is 🤖 automated reply. Set it to an
empty string to disable the signature.
When a maintainer turn starts a visible work session, its first session URL is appended to the reply unless the text already contains that URL. The canonical link is publicly readable only when the creation receipt confirms publication; otherwise it is labeled "Work session (sign-in required)." Both use X's URL-containing reply price.
For direct replies through the message tool or CLI, target the post ID with an
x: prefix or its full X status URL:
openclaw message send --channel x --target x:1234567890123456789 --message "Reply text"The target must satisfy X's reply eligibility: its author mentioned or quoted the app account. Sending media or creating an original post is unsupported.
Costs and limits
The plugin defaults to $100 per UTC day and $1,000 per billing cycle for
each account. These limits cover X API calls only; model tokens are separate.
Set costLimits.cycleStartDay to the UTC day of the month on which your X
billing cycle starts, from 1 to 28. For example, 20 makes a cycle run from the
20th at 00:00 UTC until the next month's 20th. Account entries inherit these
fields from channels.x and can override them individually.
Both limits accept nonnegative dollar amounts. 0 blocks paid requests. There
is no unlimited setting; use large limits if you want a higher ceiling. X's own
per-cycle cap still applies and can reject requests independently.
The estimates use X's published pay-per-use rates, verified October 4, 2026:
| Operation | Estimated X API price |
|---|---|
| Post read | $0.005 per returned post, including expanded posts |
| User read | $0.01 per returned user, including expanded users |
Activity post.* event | $0.005 per delivered event |
| Reply without a URL | $0.015 per reply post |
| Reply containing a URL | $0.20 per reply post |
| Empty resource response | $0 |
| Subscription management, token refresh, resource-free lists | $0 |
Thread expansion reads additional posts. A long answer creates multiple billed reply posts. Adding a handle in the allowlist UI performs a paid username lookup. These are X API costs, separate from the agent's model usage.
Spend is stored in the plugin's worker-backed state as integer micro-dollars, with separate daily and billing-cycle buckets per account. The plugin reserves the worst-case cost before each paid request and settles against returned resources, releasing any unused reservation. Concurrent requests share the same account budget. A lost response, HTTP 5xx, or unparseable success retains the full reservation because dispatch may have succeeded; only proven non-dispatch or an explicit HTTP 4xx rejection releases it without resources. Displayed spend includes pending reservations. A request spanning UTC midnight counts conservatively toward both days, but only once toward a shared billing cycle. Crossing the billing-cycle boundary counts toward both cycles. Interrupted requests retain their full reservation after a restart.
Accounting deliberately ignores X's resource deduplication within a UTC day,
so repeated reads count again. Whether X bills expanded users is not confirmed;
the plugin includes them to avoid underestimating spend, including expansions
returned with Activity events. All delivered
post.* Activity events count, even if they do not produce an agent turn. The
estimate can therefore exceed X's invoice.
X's Activity and pricing pages disagree about whether post.delete is billed;
the plugin conservatively counts it at the same rate as other post events.
Activity streaming uses a fixed $0.50 headroom. If either remaining budget falls below it, the plugin closes the stream and uses gated mention polling until the affected budget resets. Events X already delivers before the stream closes are still charged and admitted; they can push recorded spend over a limit. The headroom reduces that risk, but it is not a strict bound on a burst already delivered by X. Polling requests continue only when their full reservation fits.
When no paid poll fits, ingress pauses until reset without advancing its
since_id cursor. Already fetched mentions are durably admitted, and their
next-page token is saved beside the cursor so backfill can continue through
older pages after reset or restart. If X rejects a saved token, the plugin
restarts that backfill from since_id; the ingress queue deduplicates mentions
already admitted. Channel status reports the current
spend, limits, cycle start, and resume time, with a reason such as
X API daily budget of $100 reached; resumes at 2026-10-06T00:00Z.
The plugin logs once when a limit is reached and once when it resets. A reply
that cannot be afforded is refused with a non-retryable error; a reply chain
is charged per chunk.
When an Activity event omits mention entities, the plugin looks up the post to verify that it targets this bot. If the budget cannot cover verification, the mention is durably queued and verified after reset before any agent turn. Unverified events and events for another bot do not advance this account's cursor. If a mention entity provides only a username, the plugin performs a $0.01 user lookup to verify the numeric recipient ID; configured usernames alone cannot authorize a reply.
Configuration reference
These fields work at channels.x and on individual account entries unless noted.
| Field | Default | Purpose |
|---|---|---|
enabled | true | Enables the channel or account. |
name | Unset | Optional account display name. |
userId | Required | Numeric user ID of the bot account. |
username | Required | Bot username without @. |
clientId | Required | OAuth2 confidential application client ID. |
clientSecret | Required | Application secret; supports SecretRef. |
refreshToken | Required | Bot's user-context OAuth2 refresh token; supports SecretRef. |
bearerToken | Unset | App-only bearer for Activity and public-context verification; supports SecretRef. |
autoPublishWorkSessions | false | Publish fresh visible work sessions for verified-public maintainer mentions. |
events.mode | auto | auto, stream, or poll. |
events.pollSeconds | 60 | Mentions polling interval, minimum 15 seconds. |
allowFrom | [] | Numeric author IDs, optionally prefixed with x:. |
verifiedFromGitHub.repo | Unset (off) | Repository whose collaborators can become verified, in owner/name form. |
verifiedFromGitHub.minPermission | push | Minimum repository permission: push, maintain, or admin. |
verifiedFromGitHub.refreshMinutes | 60 | Sync interval in minutes, minimum 15. |
verifiedFromGitHub.token | Unset | GitHub read credential; supports SecretRef. |
groupPolicy | allowlist | allowlist, open, or disabled. |
dmPolicy | disabled | Only disabled is accepted. |
threadContext.maxPosts | 50 | Maximum posts included in agent thread context, from 2 to 100. |
guests.enabled | false | Enables repository-only answers for non-allowlisted authors. |
guests.maxMentionsPerAuthorPerDay | 5 | Per-author, per-account UTC-day limit, from 0 to 1000. |
guests.threadContextMaxPosts | 10 | Guest thread context cap, from 2 to 100 posts. |
guests.tools.allow | Host-supported defaults | Narrows the default guest tools; an empty array disables all tools. |
guests.tools.deny | [] | Further denies guest tools; deny wins. |
costLimits.dailyUsd | 100 | Maximum estimated X API spend per UTC day; 0 blocks paid calls. |
costLimits.monthlyUsd | 1000 | Maximum estimated X API spend per billing cycle; 0 blocks paid calls. |
costLimits.cycleStartDay | 1 | UTC billing-cycle start day of the month, from 1 to 28. |
replySignature | 🤖 automated reply | Added to the last reply chunk; up to 140 characters, empty disables it. |
accounts | Unset | Named account overrides; channel root only. |
defaultAccount | default | Account selected when none is specified; channel root only. |
Troubleshooting
No reply: check the account's status, numeric bot ID, and effective allowlist. The dropped-mention counter and last dropped author explain intentional silence. There is no pairing flow. An empty allowlist blocks all authors under the default policy.
GitHub verification is stale: inspect verifiedFromGitHub in channel status
and X replies → From GitHub for the last successful sync and error. Confirm
the token can list collaborators, its repository access is approved, and the
X API budget permits new handle lookups. The last good set remains active until
a successful sync. For an unresolved handle, check the collaborator's public
GitHub profile and that the X account exists.
Streaming falls back: check the status message for the Activity endpoint,
HTTP status, and X error detail. Verify the app bearer and the bot's OAuth2
grant, including tweet.read. Without an app bearer, auto and stream use
polling. Check the reported event mode, stream connection/backoff, last event,
and cursor.
Streaming also switches to polling when less than $0.50 remains in either
budget, and resumes after that budget resets.
Budget paused: check spend in channel status or the X replies page.
The status message gives the affected limit and reset time. Align
costLimits.cycleStartDay with your X billing cycle and increase the applicable
limit if needed. Changing limits does not erase recorded spend.
Token refresh fails: check the client ID, client secret, refresh token, and granted OAuth2 scopes. Status reports refresh state without exposing secrets.
Reply rejected: confirm the source author mentioned or quoted the app
account and the app has tweet.write. Inspect the error before retrying a
partially sent reply chain.
Failures before a reply POST can retry safely. If a POST's outcome is uncertain, OpenClaw keeps that uncertainty instead of automatically sending the reply again. Check X before manually retrying an uncertain or partially sent reply.