geode serve — Slack Gateway Operations Guide
Source: Distilled from Gateway debugging session (2026-03-26) Primary failure modes: missing binding, missing
xapp-token, or bot not invited to a bound channel
Diagnosing a connection does not authorize credential changes, channel messages, or process restarts. Apply only the requested operation and keep credential values, fragments, and private message content out of reports.
Architecture
geode serve
→ core/wiring/adapters.py: Merge ~/.geode/config.toml + project overlay
→ SlackPoller: SLACK_APP_TOKEN present → SlackSocketModeClient.run()
→ bounded-queue admit → ACK Events API envelope → filter exact bound channel → route_message()
→ _send_response(): SlackTransport.post_message (direct Web API, thread reply)
No `SLACK_APP_TOKEN` selects an explicit degraded polling fallback. It is a
migration path, not the operational target.
Prerequisites
| Item | How to verify |
|---|---|
SLACK_BOT_TOKEN |
test -n "${SLACK_BOT_TOKEN:-}" — exit status only, no value output |
SLACK_APP_TOKEN |
test -n "${SLACK_APP_TOKEN:-}" — exit status only; app requires connections:write |
| App settings | Socket Mode on; bot events app_mention, message.channels |
| Gateway config | Inspect only gateway.bindings.rules in the global config and project overlay; do not dump the entire config |
| Slack health | When live diagnostics are authorized, geode doctor slack is OPERATIONAL and every binding says bot_member=True |
The shell presence checks cover inherited environment variables only. An unset
variable does not prove the runtime lacks a credential: the resolver also uses
the global .env. Do not print or copy that file to investigate.
geode doctor slack makes live Slack calls (auth.test, apps.connections.open,
and channel membership checks). Its credential rows use partial masking, not
full redaction; workspace/bot identifiers also appear. Keep raw diagnostics
local and report only check names, status, and redacted findings. The temporary
Socket Mode URL is omitted from the successful diagnostic report.
config.toml Setup
Use .geode/config.toml.example to create a project config only when none exists.
If it already exists, edit the required binding fields in place and preserve
unrelated settings. Never overwrite an existing global config or project overlay
with the template.
[gateway.bindings]
[[gateway.bindings.rules]]
channel = "slack"
channel_id = "C0XXXXXXXXX" # Slack channel → Click channel name → Channel ID at bottom
auto_respond = true
require_mention = true # true: respond only on @mention
time_budget_s = 90 # optional per-message override; otherwise inherit gateway budget
config.tomlis in.gitignore— not deleted by git pullconfig.toml.exampleis committed — reference for clean clones- Adding channels: Repeat
[[gateway.bindings.rules]]blocks
Start/Restart
Follow Rebuild & Restart only when
startup or restart is authorized. Confirm the installation, GEODE home/socket,
PID, and session owner before stopping anything. The lifecycle implementation
in core/cli/commands/lifecycle.py currently discovers the first matching serve
PID; neither that match nor the stop command proves ownership. If several
sessions match or ownership is unclear, stop and request direction.
Restart the confirmed installation through its existing launcher and retain
startup diagnostics. Do not replace a managed service with an unrelated
background process or discard its logs into /dev/null. Verify the requested
process and socket before claiming it restarted.
Debugging Checklist
Symptom: Bot does not respond to messages
# 1. Identify the intended process/socket without stopping it (see Start/Restart).
# 2. Verify binding load
grep "binding" ~/.geode/logs/serve.log
# Expected: "Loaded N gateway bindings from config"
# If 0 → config.toml missing or parse error
# 3. Verify config source and Socket Mode
grep -i "gateway config sources" ~/.geode/logs/serve.log
# Expected: "Gateway config sources: global:... [, project:...]"
grep -E "Slack inbound mode|Slack Socket Mode connected" ~/.geode/logs/serve.log
# Expected: Socket Mode (push), then connected
# 4. Only when live Slack diagnostics are authorized; do not publish raw output.
geode doctor slack
# 5. Verify message reception after an @geode mention
grep "Slack message from" ~/.geode/logs/serve.log
Symptoms and Causes
| Symptom | Cause | Resolution |
|---|---|---|
| "Loaded 0 gateway bindings" | Binding absent or not loaded from the merged config | Check global/project sources; add only the missing binding without replacing existing config |
polling fallback |
SLACK_APP_TOKEN missing |
If configuration changes are authorized, set an app token with connections:write in the global credential store; restart only through the owned-process procedure |
not_in_channel / bot_member=False |
Bot was not invited | Run /invite @geode in the linked channel |
| Repeated disconnects | App token invalid or Socket Mode disabled | Run geode doctor slack, then verify app-level token and Socket Mode settings |
| New top-level/unengaged message receives no response | require_mention=true but no @mention |
Mention @botname once or set require_mention=false |
| Engaged thread stops after daemon restart | No resumable ACTIVE/PAUSED checkpoint, or receiver is still polling | Confirm Socket Mode logs; re-mention once if the prior machine is terminal |
| Bot re-responds to its own messages | bot_message filter bypassed | Check bot_id field — if normal, check Slack App settings |
Reaction Behavior
require_mention = true + first <@BOT_ID> mention:
- :eyes: reaction (acknowledge receipt)
- Normalize the root
tsasthread_idand remember the engaged thread ChannelManager.aroute_message()→ AgenticLoop execution- :white_check_mark: reaction (complete)
- Send response in thread
Later human reply in that engaged thread (no repeated mention):
- Match channel-scoped engaged state or the durable ACTIVE/PAUSED gateway checkpoint
- Reuse the same session/lane/checkpoint key and restore checkpoint messages after restart
- Run the same :eyes: → AgenticLoop → :white_check_mark: → thread-response lifecycle
Unengaged regular message (no mention, require_mention = false):
- Process without reaction + thread response
Related Files
| File | Role |
|---|---|
core/messaging/slack_socket_mode.py |
Socket URL, WebSocket ACK/reconnect loop |
core/server/supervised/slack_poller.py |
Socket event normalization + compatibility fallback |
core/messaging/binding.py |
Binding management + message routing |
core/messaging/slack_transport.py |
Bot-token Web API outbound + channel diagnostics |
core/wiring/adapters.py |
Gateway config merge and receiver registration |
.geode/config.toml |
Channel bindings (local, untracked) |
.geode/config.toml.example |
Binding template (committed) |