Slack approvals on your own machine¶
A spec can ask a person to approve a step in Slack: set approval_notifier and a paused run posts a message
with Approve and Reject buttons. The buttons work by Slack calling a URL on your server. That is easy on
a hosted service and awkward on a laptop, and the failures are quiet. This page is the four things that go
wrong, and what kampong does about them.
What kampong does and does not do¶
kampong does not start a tunnel. Slack needs a public HTTPS address and kampong will not open one on your
behalf, because that sends your traffic through a third party you did not choose and kampong makes no network
connection you did not ask for (ADR-0049). Instead:
kampong serveprints, once at startup, exactly what Slack needs (below), and warns ifSLACK_SIGNING_SECRETis not set.kampong devsays the canvas uses its own in-app approval dialog, so nothing needs to be public there, and points here for the Slack buttons.- A paused run never depends on Slack. You can always resolve it with
POST /runs/<id>/approveorPOST /approvals/<key>/approve({"approved": true}), which is also how you test everything except the buttons.
The four traps¶
1. Slack cannot reach localhost. The Interactivity Request URL must be public HTTPS. Run kampong serve
and expose its port with a tunnel you already trust (ngrok, cloudflared, a reverse proxy on a server), then set
the Request URL to https://<your-public-host>/slack/interactions.
2. A relay can break the signature, and kampong repairs it. Slack signs the exact bytes of its
form-encoded request (payload=<url-encoded JSON>). Some relays (smee.io is one) parse that body and forward it
as JSON, so the bytes Slack signed are gone and the check can never pass. kampong serve and an exported
project rebuild the signed bytes from the JSON (payload= plus the RFC 3986 encoding Slack uses, spaces as +)
and then verify the signature against them, exactly as before. The check stays fully on: an altered click, or a
forged one, is still refused with 401. A tunnel that forwards the request untouched (ngrok, cloudflared)
needs none of this.
3. Socket Mode silently turns the buttons off. With Socket Mode on, Slack delivers clicks over a websocket and never makes an HTTP request, so nothing arrives, nothing is logged, and the run just waits. In the Slack app settings, turn Socket Mode off.
4. An app has one Interactivity Request URL. Pointing a second environment (your laptop, then staging) at the same Slack app means one of them stops receiving clicks, with no error. Use a separate Slack app per environment, each with its own bot token and signing secret.
Setting it up¶
- Create the Slack app, add the
chat:writescope (andchat:write.public, or invite the bot to the channel), install it, and copy the Bot User OAuth Token and the Signing Secret. - Export both where
kampong serveruns:
- Start the service, and expose it:
kampong serve agent.yaml --port 8080
ngrok http 8080 # or cloudflared tunnel --url http://localhost:8080
- In the Slack app, Interactivity & Shortcuts: turn it on, set the Request URL to
https://<the-public-host>/slack/interactions, and confirm Socket Mode is off. - Trigger a run that reaches the approval. The message appears in the channel; click a button.
If a click does nothing, check in this order: Socket Mode off, the Request URL is the current tunnel address
(a free tunnel changes it on restart), SLACK_SIGNING_SECRET is set in the process that is serving, and the
server log for a 401 on /slack/interactions.
A run that waits across a restart¶
A pending approval is stored in .kampong/state.db, so it survives a restart of kampong serve: the buttons on
the Slack message, or POST /approvals/<key>/approve, still resolve it. Keep .kampong/ on a volume in a
container.