Slack direct messages¶
Push supports one-to-one Slack app direct messages. It receives events through
Socket Mode and
sends replies with chat.postMessage.
It ignores public and private channels, group DMs, slash commands, and bot
messages.
Create the Slack app¶
- Create a Slack app for one workspace and enable Socket Mode.
- Create an app-level token with
connections:write. App tokens start withxapp-. - Add the bot token scopes
im:historyandchat:write. - Under Event Subscriptions, subscribe to the bot event
message.im. - Install or reinstall the app and open its Messages tab.
- Copy your stable Slack member ID, such as
U012ABCDEF, from your profile.
These are the only Slack scopes needed. Do not add channel, group-DM,
chat:write.public, or user-token scopes.
Configure Push¶
Prefer environment variables for both secrets:
Configure the channel and an explicit user allowlist:
channel = "slack"
agent = "codex"
assistant_root = "~/Code/assistant"
[slack]
allow_user_ids = ["U012ABCDEF"]
You can instead set slack.app_token and slack.bot_token in the private Push
config. Never put tokens in the Git-versioned assistant repository. Run:
At runtime Push checks the bot token with Slack's
auth.test method and
binds the connection to that workspace and bot user. Usernames and display
names are not accepted in the allowlist.
Delivery and recovery¶
Push validates the workspace, message shape, direct-message channel type, sender ID, and bot origin before an event can reach an agent. Ordinary text messages with no Slack message subtype are the supported first phase.
Each conversation uses the stable key
slack:dm:<workspace-id>:<dm-channel-id>. Replies go to the Slack thread rooted
at the originating message. Push uses
assistant.threads.setStatus
as a best-effort progress signal. A status failure never blocks the final
reply.
Before acknowledging an accepted Socket Mode envelope, Push commits it to a
private SQLite inbox beside state_path. Ignored envelopes retain only redacted
rejection metadata, not message content, before they are acknowledged. Slack's globally unique Events API
event_id is the durable deduplication key, while a local monotonic row ID
drives ordered cursor recovery. A crash before acknowledgement causes Slack to
redeliver the same event; a restart resumes committed rows above the saved cursor. Slack does
not document an idempotency key for chat.postMessage, so a network failure
after Slack accepts a send can still produce an ambiguous delivery.
Web API rate limits are retried once using Slack's Retry-After header, then
the normal Push delivery retry path applies. Replies are split at 4,000 Unicode
characters. Slack voice messages and replies are not supported.
For scheduled delivery, use an allowlisted Slack user ID:
Slack accepts the user ID as the chat.postMessage destination and opens the
app's direct-message conversation.
Troubleshooting¶
- If
Slack app tokenfails inpush doctor, setSLACK_APP_TOKENorslack.app_token. - If
Slack bot tokenfails, setSLACK_BOT_TOKENorslack.bot_token. - If messages are ignored, confirm
message.im,im:history, the exact member ID, and that the app was reinstalled after scope changes. - If Slack returns
missing_scope, confirmconnections:writeis on the app token andim:historypluschat:writeare on the bot token. - Reconnect messages are expected because Slack refreshes Socket Mode connections periodically. Push's dedicated receiver reconnects automatically.