Skip to content

Fix invalid_blocks: reject numeric-only emoji shortcodes - #7

Closed
presmihaylov wants to merge 12 commits into
navidemad:mainfrom
presmihaylov:fix/numeric-emoji-shortcode-false-positive
Closed

Fix invalid_blocks: reject numeric-only emoji shortcodes#7
presmihaylov wants to merge 12 commits into
navidemad:mainfrom
presmihaylov:fix/numeric-emoji-shortcode-false-positive

Conversation

@presmihaylov

Copy link
Copy Markdown
Contributor

Summary

  • Time strings like 19:49:41 in inline code were being split into false emoji matches (:49: → emoji element with name "49"), causing Slack to reject the entire message with invalid_blocks
  • Added isValidEmojiName() check requiring at least one letter in the emoji name, which correctly excludes numeric-only patterns while preserving valid emojis like :+1:
  • Added regression tests for time-like patterns in inline code

Root Cause

The emoji shortcode regex :[a-z0-9+][a-z0-9_+\-]*: matched pure-digit patterns like :49: and :50: extracted from timestamps. Slack's API rejects these as invalid emoji element names since no such emojis exist.

Test plan

  • Existing emoji shortcode tests pass (:bar_chart:, :+1:, :wave:, etc.)
  • New test: numeric-only colons in inline code not treated as emoji
  • New test: time-like patterns (10:30:00, 23:59:59) not treated as emoji
  • Reproduced original failure with Slack webhook (400 invalid_blocks)
  • Verified fix with same webhook (200 ok)
  • Full test suite passes including fuzz tests

🤖 Generated with Claude Code

presmihaylov and others added 12 commits February 28, 2026 12:37
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tables previously rendered as ASCII art inside code-fenced SectionBlocks,
which wrapped poorly for long cell values. Slack's native TableBlock
(added Aug 2025) provides per-column wrapping and alignment. This change
upgrades slack-go to v0.18.0 and rewrites table rendering to use it.

Cells are now *RichTextBlock, so inline formatting (bold, links, code)
is preserved automatically instead of being stripped to plain text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Empty cells now get a minimal RichTextSection instead of producing
  "elements": null which the Slack API would reject
- Update Go version requirement from 1.22+ to 1.25+ in CLAUDE.md,
  README.md, and DEMO.md to match go.mod
- Fix stale doc.go prose still referencing SectionBlock for tables
- Update README.md table mapping for GFM tables
- Add TestConvert_TableEmptyCell test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Slack rejects messages with more than one table
(only_one_table_allowed). ChunkBlocks now forces a new chunk before
every additional TableBlock, in addition to the existing maxPerMessage
split.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add nil guard to emitBlock to prevent appending nil blocks
- Reset style stack at table cell entry to prevent style leaking
- Document ChunkBlocks table-per-chunk enforcement in README
- Add tests for multiple tables, mixed blocks, and code-in-cell

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Incorporates upstream feat/native-table-block changes (GFM tables now
render as native Slack TableBlocks with rich text cells and per-column
alignment). Fixes empty cell handling — Slack API rejects empty string
text elements, so use a space instead. Updates module path to fork.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Slack's rich_text blocks require emojis as explicit RichTextSectionEmojiElement
objects — plain text shortcodes like 📊 are rendered literally.
Goldmark's emphasis parser also splits text at underscores, fragmenting
shortcodes across multiple text elements.

Add post-processing in resolveEmojis() that merges adjacent text elements
with the same style and converts detected :emoji_name: patterns into
proper emoji elements. Applied in flushInlineToSection(), paragraph
emission, and blockquote accumulation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert emoji shortcodes to native Slack emoji elements
Slack's TableBlock API enforces a maximum of 100 rows (including
header) and 20 columns per table. Previously, md2slack would emit
a single TableBlock regardless of size, causing Slack to reject the
message with `invalid_blocks`.

Split oversized tables into multiple TableBlocks, each carrying the
same header row. Columns beyond 20 are silently truncated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enforce Slack TableBlock row and column limits
Time strings like `19:49:41` in inline code were being split into
false emoji matches (`:49:` → emoji element with name "49"), causing
Slack to reject the entire message with `invalid_blocks`.

Add isValidEmojiName check requiring at least one letter in the emoji
name, which correctly excludes numeric-only patterns while preserving
valid emojis like `:+1:`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants