Skip to content

chore: migrate Slack runtime from Hubot RTM to Bolt - #55

Merged
alronova merged 12 commits into
mdgspace:masterfrom
dhairyathareja1:revamp/bolt
Sep 24, 2026
Merged

alronova merged 12 commits into
mdgspace:masterfrom
dhairyathareja1:revamp/bolt

Conversation

@dhairyathareja1

Copy link
Copy Markdown
Contributor

Summary

This PR resolves #50 by replacing the deprecated hubot-slack RTM integration with Bolt for JavaScript and Slack's HTTP Events API.

I also implemented the Node.js upgrade tracked in #49. This Bolt branch is built on that Node 24 work, and both PRs are intended to be merged together because Bolt v5 requires Node.js 20 or newer.

The existing command modules and Redis memory format are retained through an internal compatibility runtime. Hubot, hubot-slack, hubot-redis-brain, and the Hubot runtime are no longer production dependencies.

Closes #50
Related to #49

What changed

Bolt migration

  • Replaced hubot-slack with @slack/bolt.
  • Added a Bolt application entrypoint using the HTTP Events API.
  • Added the signed /slack/events endpoint.
  • Migrated message, app_mention, and user_change handling to Bolt.
  • Replaced the Hubot router with Bolt's Express receiver while preserving the existing custom HTTP routes.
  • Replaced adapter-specific Hubot types and behavior with local runtime interfaces.
  • Preserved the existing command registration, middleware, response, listener, brain, HTTP helper, and Slack transport APIs used by the scripts.
  • Kept the old bin/hubot paths as compatibility launchers that forward to Bolt without loading Hubot.

Event processing and Redis

  • Events are written to a durable Redis inbox before Slack receives HTTP 200.
  • Duplicate message and app_mention deliveries for the same Slack message are processed once.
  • Messages remain ordered within each channel.
  • One instance can process up to eight channels concurrently.
  • Failed events use exponential backoff and move to a bounded dead-letter inbox after 12 attempts.
  • A renewable Redis lease prevents multiple Bolt instances from processing the same brain.
  • Lease renewal continues through graceful shutdown and the final brain save.
  • Existing inbox entries are upgraded by rebuilding derived scheduler indexes at startup.
  • The existing brain key and Redis URL-prefix behavior are preserved, including the default hubot:storage key.

Compatibility and hardening

  • Existing public-channel commands, scheduled jobs, brain mutations, attachments, and custom webhook routes remain supported.
  • Incoming DMs and private-channel commands remain disabled, matching the existing middleware behavior.
  • file_share, bot messages, mentions, channel lookups, user lookups, and thread context are handled by the new adapter.
  • Long Slack messages are split into ordered 4,000-character chunks.
  • Outbound work is serialized per destination rather than globally.
  • HTTP helpers now follow redirects and use a timeout.
  • env, show users, show storage, and die require IDs configured through BOT_ADMIN_IDS.
  • Environment output and stored diagnostics redact credentials and sensitive URLs.
  • Environment file loading is restricted to HUBOT_ENV_BASE_PATH.
  • Slack profiles no longer store email addresses or complete raw profiles in the brain.

Node.js and deployment

  • Upgraded Node.js from 8.10 to 24.19.0.
  • Upgraded npm to 11 and TypeScript to 5.9.
  • Added .nvmrc and aligned local development and CI versions.
  • Added a multistage Docker build running as the unprivileged node user.
  • Updated Docker Compose, start_bot.sh, Procfile, npm scripts, and Windows/Unix launchers.
  • Added automatic .env loading for local launch paths.
  • Preserved all existing deployment paths.
  • Removed obsolete Hubot dependencies and replaced vulnerable legacy HTML parsing packages.

Slack configuration

The new Slack app uses:

  • SLACK_BOT_TOKEN
  • SLACK_SIGNING_SECRET
  • A public /slack/events Request URL

Socket Mode is not used, so SLACK_APP_TOKEN is not required.

Subscribed bot events:

  • app_mention
  • message.channels
  • user_change

The new bot must be invited to every channel where it should receive messages or post scheduled output, including #general.

The app should be installed in the same workspace as the existing bot so stored Slack user IDs remain valid.

Deployment notes

  • Keep the existing Redis URL path/key prefix to preserve the current brain.
  • Production Redis must be reachable from the bot container and should use persistence with a non-evicting policy.
  • Stop the old Hubot process before starting Bolt. The old runtime does not participate in the new Redis lease.
  • Configure BOT_ADMIN_IDS for administrative commands.
  • The supplied Compose configuration maps host port 9998 to container port 8080.
  • A crash after an external side effect but before inbox completion can replay that event; processing is at least once.

Verification

  • npm run check
  • 99 tests passing
  • TypeScript type-check passes
  • Real Redis inbox and Lua operations tested against Redis 7 in CI
  • Production Docker image builds and passes its compiled-module smoke test
  • npm audit --omit=dev reports 0 vulnerabilities
  • Tests use fake Slack clients and do not connect to a live workspace

Implement local command dispatch, script interfaces, HTTP helpers, and legacy-compatible brain persistence without importing Hubot. Preserve existing command implementations and replace only their type imports; this is not a complete rewrite of the scripts.

Keep the existing Hubot startup and dependencies temporarily until the Bolt cutover. Add 29 offline runtime regression tests; all 43 tests pass.
Add the signed Bolt HTTP Events API receiver, Slack event normalization, message delivery, persistent event deduplication, and Redis-backed brain storage while retaining the existing memory format and key.

Keep construction offline and cover Slack and Redis behavior with mocked integration tests. The deployment launcher cutover and removal of temporary Hubot dependencies remain separate.
Persist Slack events before acknowledgement, process channels independently, and retain transient failures for retry.

Secure privileged commands, improve transport and HTTP reliability, clean production dependencies, and expand regression coverage.
Coalesce brain snapshots, process bounded channel batches, and move profile updates out of the durable command queue.

Add durable retry limits, dead-letter storage, a renewable single-worker lease, startup validation, and regression coverage.
@alronova

Copy link
Copy Markdown
Member

hi @dhairyathareja1, here are a few findings which need to be resolved, have a look into these:

  • Finding 1 (high): src-ts/events.ts:16: the fake event command emits any event name the user types, and main.ts now treats shutdown as "exit the process" — so bot fake event shutdown lets any channel member kill the bot, bypassing the new admin gate on die. Reproduced against the compiled build; it contradicts the assertion in test/cutover-regressions.js:191. Your claim for BOT_ADMIN_IDS is false in practice. As a fix, you can gate it with the adminGuard() fn.
  • Finding 2 (medium): src-ts/runtime/bolt-app.ts:218: when the Redis worker lease is lost rather than released, the same shutdown path still writes the final brain snapshot (and drains batches that keep calling save()/completeEvent()), so a replacement instance's memory can be overwritten with stale data.

@alronova

Copy link
Copy Markdown
Member

Also, I was expecting that this PR should have been branched out from master but it shares the base as the changes made in #51. As of now, #51 is already merged with master, so it'd be better if you rebase this PR with master only.

@dhairyathareja1
dhairyathareja1 force-pushed the revamp/bolt branch 2 times, most recently from 78fb253 to 223950a Compare September 22, 2026 23:27
Resolve member names and mentions for keys, info, and scores while preserving username-keyed brain data. Keep update-db replies in their originating thread and make member-sheet reads reliable for local testing. Add regressions for the restored behavior.
@alronova
alronova merged commit 9a406e1 into mdgspace:master Sep 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chore: Change slack adapter from hubot to bolt

2 participants