Skip to content

Fix Slack daily-summary crash (OTP TLS handshake) + unblock CI#560

Merged
marcdel merged 2 commits into
mainfrom
fix/otp-tls-slack-handshake
Jun 10, 2026
Merged

Fix Slack daily-summary crash (OTP TLS handshake) + unblock CI#560
marcdel merged 2 commits into
mainfrom
fix/otp-tls-slack-handshake

Conversation

@marcdel

@marcdel marcdel commented Jun 10, 2026

Copy link
Copy Markdown
Owner

What broke

A user on team titus reported that clicking Save in Pears did nothing — no daily-pairs message posted to Slack, and the browser console showed Uncaught (in promise) Object { timeout: true }.

Root cause (reproduced)

Save → record-pearsSlack.send_daily_pears_summarySlackClient.send_message makes a synchronous outbound HTTPS call to Slack from inside the LiveView process. On OTP 27.2.1 that TLS handshake now fails and raises, crashing the handle_event/3 handler — so the browser never gets a reply ({timeout: true}) and nothing posts.

The failure is a fatal TLS alert:

{:tls_alert, {:unsupported_certificate, ... key_usage_mismatch ...}}

Slack's (recently rotated) cert chain includes an intermediate carrying serverAuth EKU alongside CA key usage (keyCertSign, cRLSign). OTP 27.2.x has a cert-validation bug that wrongly rejects this; it's fixed in 27.3+. This broke without any deploy because the trigger was Slack's cert rotation, not our code.

Reproduced with a bare :ssl.connect to api.slack.com:

OTP Result
27.2.1 (old pin) key_usage_mismatch
27.3.4.11 ✅ handshake OK
28.5 ✅ handshake OK

The fix

  • Bump OTP 27.2.1 → 27.3.4.13 in .tool-versions, the CI matrix, and the Dockerfile builder image (debian snapshot bumped to a matching published hexpm tag).

Also unblocks CI/deploy (main was red, so this couldn't ship)

  • Drop unused :castore from mix.lock (mix deps.unlock --check-unused was failing after the Phoenix 1.8 upgrade).
  • config/runtime.exs was fetch_env!-ing SLACK_CLIENT_ID/SECRET/SIGNING_SECRET unconditionally, crashing boot in test/CI when SLACK_SIGNING_SECRET is unset. Now required only in :prod; dev/test use placeholders.

⚠️ Required before this deploys to prod

The Fly app pears-app is missing the SLACK_SIGNING_SECRET secret (it has SLACK_CLIENT_ID and SLACK_CLIENT_SECRET). Prod config stays strict, so the app will fail to boot until you set it:

fly secrets set SLACK_SIGNING_SECRET=<value> -a pears-app

Verification

  • mix deps.unlock --check-unused, mix compile --warnings-as-errors, mix format --check-formatted, mix credo --strict: all green
  • mix test: 288 tests, 0 failures, 4 skipped
  • OTP 27.3.x handshake to Slack verified locally (table above)

Follow-up (not in this PR)

Harden record-pears so a Slack failure can't crash/block the LiveView again: move the send off the reply path and surface a flash on failure, and fix the non-{:noreply} fallback branch at pairing_board_live.ex:126.

🤖 Generated with Claude Code

marcdel and others added 2 commits June 10, 2026 10:40
OTP 27.2.x has a TLS certificate-validation bug that rejects Slack's
(recently rotated) certificate chain with a fatal `key_usage_mismatch`
alert — an intermediate carrying serverAuth EKU alongside CA key usage.
This raised out of SlackClient.send_message and crashed the record-pears
LiveView handler when posting the daily pairs summary, so "Save" timed
out in the browser ({timeout: true}) and no message posted.

Reproduced with a bare :ssl.connect to api.slack.com:
  - OTP 27.2.1:            fails (key_usage_mismatch)
  - OTP 27.3.4.11 / 28.5:  handshake succeeds

Bumps the pin in .tool-versions, both CI matrices, and the Dockerfile
builder image (with the matching debian-bookworm-20260518 snapshot that
has a published hexpm image; runner image moves with it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two issues kept main red and undeployable:

- `mix deps.unlock --check-unused` failed because :castore became unused
  after the Phoenix 1.8 upgrade. Removed it from the lock.
- config/runtime.exs called System.fetch_env! for SLACK_CLIENT_ID,
  SLACK_CLIENT_SECRET and SLACK_SIGNING_SECRET unconditionally, crashing
  the app at boot in test/CI whenever SLACK_SIGNING_SECRET is unset. These
  are now required only in :prod; dev/test fall back to placeholders.

Note: prod (fly app pears-app) is still missing the SLACK_SIGNING_SECRET
secret, so it must be set before the next deploy will boot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@marcdel marcdel merged commit 47402d3 into main Jun 10, 2026
4 checks passed
@marcdel marcdel deleted the fix/otp-tls-slack-handshake branch June 10, 2026 18:04
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.

1 participant