May 10, 2026

Slack Message Double HTML Encoding: & Appears Instead of &

When sending messages to Slack, ampersand characters are double-encoded due to pre-escaping in OpenClaw combined with Slack API escaping, resulting in & displaying instead of &.

πŸ” Symptoms

Visual Manifestation

When sending messages containing ampersands to Slack, users observe literal & text appearing in the channel instead of the expected & character.

Example Input:

Project: R&D Budget
Contact: John & Jane
Links: https://example.com/?a=1&b=2

Expected Output in Slack:

Project: R&D Budget
Contact: John & Jane
Links: https://example.com/?a=1&b=2

Actual (Broken) Output in Slack:

Project: R&D Budget
Contact: John & Jane
Links: https://example.com/?a=1&b=2

Technical Error Sequence

The double-encoding occurs through the following pipeline:

  1. Input: & (literal ampersand)
  2. After OpenClaw escapeHtml(): &
  3. After Slack API processing: &
  4. Rendered in Slack: & (literal string)

Affected Message Patterns

This issue manifests in common scenarios:

  • Company names: R&D, B&B, P&G
  • Names: John & Jane
  • URL query parameters: ?a=1&b=2
  • Text with "and" alternatives: Rock & Roll

🧠 Root Cause

Architectural Analysis

The issue stems from a redundant encoding layer in the OpenClaw send module. The escapeHtml() function in dist/send-*.js pre-emptively escapes HTML entities before dispatching to the Slack API, which independently performs HTML entity encoding.

Code Flow Analysis

OpenClaw’s escapeHtml() implementation:

function escapeHtml(text) {
  return text
    .replace(/&/g, "&")
    .replace(//g, ">");
}

Slack API’s documented behavior: Per Slack API documentation, incoming webhook payloads and API messages require & to be encoded as &. The Slack API performs this encoding server-side.

Double-Encoding Mechanism

The encoding pipeline operates as follows:

StageInputOperationOutput
1. User InputR&Dβ€”R&D
2. OpenClaw escapeHtml()R&DReplace & β†’ &R&D
3. Slack APIR&DReplace & β†’ & (within non-& contexts)R&D
4. Slack RenderR&DDecode as literal textR&D (visible)

Why & Appears

The Slack API interprets the sequence as follows:

  • The string contains & (which Slack treats as an escaped ampersand)
  • Slack then escapes the & within that sequence from & β†’ &
  • This creates &, which Slack displays as literal text rather than decoding as &

Affected Files

The problematic code exists in:

  • dist/send-slack.js β€” Slack-specific send module
  • dist/send-generic.js β€” Generic channel send module (may affect other integrations)
  • Potentially src/send/slack.ts or equivalent source files

πŸ› οΈ Step-by-Step Fix

Modify the send module to bypass escapeHtml() specifically for Slack integration.

Before (problematic):

// In dist/send-slack.js
function buildPayload(message) {
  return {
    text: escapeHtml(message)  // ❌ Causes double-encoding
  };
}

After (fixed):

// In dist/send-slack.js
function buildPayload(message) {
  return {
    text: message  // βœ… Let Slack handle encoding
  };
}

Option 2: Conditional Escaping with Detection

Implement smarter escaping that detects already-escaped sequences:

Proposed fix:

function escapeHtmlSmart(text) {
  // Don't double-encode already-escaped sequences
  return text
    .replace(/&(?!amp;|lt;|gt;|quot;|#[0-9]+;)/g, "&")
    .replace(//g, ">");
}

Option 3: Environment-Aware Encoding

Add a configuration flag to control escaping behavior per channel:

Configuration addition:

// config/channels.json
{
  "slack": {
    "requiresHtmlEscape": false
  },
  "webhook": {
    "requiresHtmlEscape": true
  }
}

Implementation:

function buildPayload(message, channelConfig) {
  const text = channelConfig.requiresHtmlEscape 
    ? escapeHtml(message) 
    : message;
  
  return { text };
}

Immediate Hotfix (Production)

If the issue requires immediate resolution without redeployment:

  1. Identify the affected message sources
  2. Temporarily replace `&` with a placeholder (e.g., `%26`) before OpenClaw processing
  3. Add a post-processing step after Slack receives to reverse the placeholder

Warning: This is a temporary measure only; implement a proper fix within 24-48 hours.

πŸ§ͺ Verification

Test Case 1: Basic Ampersand

Command:

curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
  -H 'Content-type: application/json' \
  -d '{"text": "John & Jane"}'

Expected Output in Slack: John & Jane

Acceptance Criteria: No & or & visible in the message

Test Case 2: Multiple Ampersands in URL

Command:

curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
  -H 'Content-type: application/json' \
  -d '{"text": "https://api.example.com?a=1&b=2&c=3"}'

Expected Output in Slack: Full URL with properly rendered & characters

Test Case 3: Company Names with Ampersands

Command:

curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
  -H 'Content-type: application/json' \
  -d '{"text": "Partners: AT&T, P&G, B&M"}'

Expected Output in Slack: AT&T, P&G, B&M rendered correctly

Automated Verification Script

#!/bin/bash
# test-slack-encoding.sh

TEST_CASES=(
  "John & Jane"
  "R&D Budget 2024"
  "https://example.com/?x=1&y=2"
  "AT&T | P&G | B&M"
)

WEBHOOK_URL="${SLACK_WEBHOOK_URL}"

for test in "${TEST_CASES[@]}"; do
  echo "Testing: $test"
  response=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST "$WEBHOOK_URL" \
    -H 'Content-type: application/json' \
    -d "{\"text\": \"$test\"}")
  
  if [ "$response" == "200" ]; then
    echo "  βœ“ HTTP 200 - Message sent"
  else
    echo "  βœ— HTTP $response - Failed"
  fi
done

Run verification:

chmod +x test-slack-encoding.sh
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL" ./test-slack-encoding.sh

Post-Fix Validation Checklist

  • β–‘ No `&` strings appear in Slack messages
  • β–‘ No `&` sequences visible in message source
  • β–‘ URLs with query parameters render correctly
  • β–‘ Company names with `&` display correctly
  • β–‘ Other HTML entities (`<`, `>`) still render appropriately
  • β–‘ Exit code 0 from automated tests

⚠️ Common Pitfalls

1. Platform-Specific Encoding Differences

Pitfall: Removing escapeHtml() entirely may break other integrations that rely on pre-escaping.

Mitigation: Implement channel-specific encoding flags rather than global changes.

// ❌ Wrong: Breaks other channels
// const escapeHtml = (text) => text.replace(...);

// βœ… Correct: Channel-aware escaping
function getEncoder(channel) {
  const noEscape = ['slack', 'teams'];
  return noEscape.includes(channel) 
    ? (text) => text 
    : escapeHtml;
}

2. Case Sensitivity in Entity Detection

Pitfall: The negative lookahead in Option 2 may miss uppercase variants.

Issue: Slack may receive or produce &AMP; or &AMP; in edge cases.

Mitigation: Normalize to lowercase before checking:

.replace(/&(?!amp;|lt;|gt;|quot;)/gi, "&")
//                     ^^ Add case-insensitive flag

3. Nested or Partial Escaping

Pitfall: Text may contain partially-escaped content from prior processing.

Example: Input &amp;Hello (already escaped) should remain &amp;Hello, not become &amp;amp;Hello.

Detection: Use regex to identify standalone & not followed by valid entity patterns.

4. Docker Container Caching

Pitfall: After fixing the source code, Docker containers may serve cached versions.

Resolution:

docker-compose down
docker-compose build --no-cache openclaw
docker-compose up -d

5. Environment Variable Configuration

Pitfall: If using environment variables to control escaping behavior, ensure they propagate correctly in containerized environments.

Check:

docker exec -it openclaw-container env | grep HTML
# Should show: HTML_ESCAPE_REQUIRED=false

6. Regression Testing Gaps

Pitfall: Fixing Slack encoding may inadvertently affect other channels (email, webhooks, etc.).

Mitigation: Run integration tests against all configured channels after any encoding-related changes.

  • SLACK-API-002: Block Kit Entity Encoding
    When using Slack Block Kit with `mrkdwn` type blocks, additional escaping rules apply. Text in `mrkdwn` blocks may require different handling than plain `text` fields.
  • ESCAPE-001: Inconsistent Escape Behavior Across Channels
    Different channel integrations (Slack, Teams, Email) have varying HTML encoding requirements, leading to inconsistent message rendering.
  • API-017: Webhook Payload Double-Encoding
    Similar double-encoding issue reported for generic webhook integrations when both client and server apply HTML escaping.
  • HTML-INJECTION-003: Unescaped `<` and `>` in Slack Messages
    Prior to the `escapeHtml()` addition, raw `<` and `>` characters could potentially inject HTML-like content. The fix introduced the ampersand double-encoding issue.
  • CHARACTER-SET-006: UTF-8 Encoding Issues with Special Characters
    Unicode characters (emojis, accented letters) occasionally displayed incorrectly when passed through the encoding pipeline.
  • BUFFER-OVERFLOW-001: Long Messages Truncation
    When `escapeHtml()` increased message length, messages exceeding Slack's 3001 character limit were silently truncated.

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.