Skip to content

Wrong caching of dropdown in slack Block message #2766

@tarun-truva

Description

@tarun-truva

Bug Report: static_select initial_option Not Persisting After chat.update on Tab Switch

Summary

When updating a message containing a static_select element via chat.update API with a new initial_option, the selection appears correct immediately after the update. However, when users switch tabs or conversations and return, the dropdown reverts to the previous selection, even though the server-side message state is correct (verified by full page refresh).

Environment

  • Slack API: Web API
  • Method: chat.update
  • Block Element: static_select
  • Client: Slack Desktop App (macOS), Slack Web
  • Slack API Package: @slack/web-api v7.9.2
  • Node.js: v18+

Steps to Reproduce

  1. Send initial message with a static_select dropdown and action buttons:
const blocks = [
  {
    type: 'context',
    block_id: 'assignment_status',
    elements: [{
      type: 'mrkdwn',
      text: '👤 *Status:* Unassigned'
    }]
  },
  {
    type: 'actions',
    block_id: 'assignment_actions',
    elements: [
      {
        type: 'button',
        action_id: 'assign_to_me',
        text: { type: 'plain_text', text: '🙋 Assign to me' },
        style: 'primary'
      },
      {
        type: 'static_select',
        action_id: 'assign_user',
        placeholder: { type: 'plain_text', text: 'Assign to team member' },
        options: [
          { text: { type: 'plain_text', text: 'User A' }, value: 'user_a_id' },
          { text: { type: 'plain_text', text: 'User B' }, value: 'user_b_id' },
          { text: { type: 'plain_text', text: 'User C' }, value: 'user_c_id' }
        ]
      }
    ]
  }
]
  1. User selects "User A" from dropdown

    • Backend receives block_actions payload
    • Backend calls chat.update with initial_option set to User A
    • ✅ Dropdown correctly shows "User A" selected
  2. Switch to another Slack channel, then switch back

    • ✅ Dropdown still shows "User A" (correct)
  3. User clicks "Assign to me" button (to assign to User B)

    • Backend receives block_actions payload
    • Backend rebuilds static_select with fresh options array
    • Backend explicitly sets initial_option to User B (with matching option from options array)
    • Backend calls chat.update with complete blocks
    • ✅ Dropdown immediately shows "User B" selected
  4. Switch to another Slack channel, then switch back

    • ❌ Dropdown shows "User A" (wrong!)
    • ✅ Status text shows "User B" (correct - from context block)
  5. Refresh the entire Slack page/app

    • ✅ Dropdown now shows "User B" (correct)

Expected Behavior

After chat.update is called with a new initial_option, the static_select dropdown should display the newly selected option consistently across:

  • Immediate rendering after update ✅
  • Tab switches ❌ (BUG)
  • Page refreshes ✅

Actual Behavior

The static_select dropdown reverts to a previously cached selection after tab switches, despite:

  • The chat.update API call succeeding
  • Other blocks in the same message updating correctly
  • Full page refresh showing the correct selection
  • The server-side message state being correct (verified via conversations.history API)

Code Example

Backend Action Handler

async function handleSlackAction({ payload }) {
  const channel = payload.channel.id
  const ts = payload.message.ts
  const messageBlocks = payload.message.blocks
  
  // User clicked "Assign to me" button
  if (payload.actions[0].action_id === 'assign_to_me') {
    // Fetch fresh list of users
    const users = await fetchAllUsers() // Returns: [{ id: 'user_a_id', name: 'User A' }, ...]
    
    // Find the assignment blocks
    const statusIndex = messageBlocks.findIndex(b => b.block_id === 'assignment_status')
    const actionsIndex = messageBlocks.findIndex(b => b.block_id === 'assignment_actions')
    
    // Update status block
    messageBlocks[statusIndex] = {
      type: 'context',
      block_id: 'assignment_status',
      elements: [{
        type: 'mrkdwn',
        text: '👤 *Status:* User B'  // New assignee
      }]
    }
    
    // Rebuild static_select with explicit properties (no spreading)
    messageBlocks[actionsIndex] = {
      type: 'actions',
      block_id: 'assignment_actions',
      elements: [
        {
          type: 'button',
          action_id: 'assign_to_me',
          text: { type: 'plain_text', text: '🙋 Assign to me' },
          style: 'primary'
        },
        {
          type: 'static_select',
          action_id: 'assign_user',
          placeholder: { type: 'plain_text', text: 'Assign to team member' },
          options: users.map(u => ({
            text: { type: 'plain_text', text: u.name },
            value: u.id
          })),
          initial_option: {  // Setting to User B
            text: { type: 'plain_text', text: 'User B' },
            value: 'user_b_id'
          }
        }
      ]
    }
    
    // Update message
    await slack.chat.update({
      channel,
      ts,
      blocks: messageBlocks,
      text: 'Meeting assigned'
    })
    
    // Return empty response (tried returning blocks too - same issue)
    return new Response('', { status: 200 })
  }
}

What We've Tried

  1. Explicitly reconstructing the element (no object spreading):

    return {
      type: 'static_select',
      action_id: el.action_id,
      placeholder: el.placeholder,
      options: el.options,
      initial_option: matchingOption  // Taken from options array
    }
  2. Re-fetching options from database (not from payload):

    • Ensures fresh options array with no stale references
  3. Deep cloning all blocks:

    const nextBlocks = JSON.parse(JSON.stringify(blocks))
  4. Returning blocks in response vs not returning blocks:

    • Both exhibit the same issue
  5. Using initial_option that matches options array:

    const matchingOption = el.options.find(opt => opt.value === newUserId)
    return { ...el, initial_option: matchingOption }

Additional Observations

  1. Other block types update correctly: The context block (status text) always shows the correct value after tab switch

  2. Full refresh works: Suggests server state is correct, but client cache is stale

  3. Issue is specific to tab switching: Direct interaction after update shows correct state

  4. Happens on both Desktop and Web clients: Not platform-specific

  5. Only affects static_select after button interactions: Dropdown-to-dropdown selections persist correctly across tab switches

Hypothesis

Slack's client appears to cache static_select state independently from the message blocks returned by the server. When a tab switch occurs:

  1. Client checks its cache for the message
  2. For static_select elements, it may be using a cached selection state that wasn't properly invalidated
  3. Other block types re-render from server state correctly
  4. Full page refresh clears all caches and fetches fresh from server

Impact

  • Severity: High - Causes user confusion when UI shows incorrect state
  • Frequency: Reproducible 100% of the time with the specific interaction pattern
  • User Experience: Users see wrong assignments after tab switches, requiring page refresh

Expected Fix

The static_select element should respect the initial_option value from the server-side message state (as returned by chat.update) when re-rendering after tab switches, consistent with how other block elements behave.

Screen.Recording.2026-01-23.at.12.17.47.PM.mov

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions