Skip to content

GetThreadContext returns None in user_message handler #1452

Closed

Description

@jczhong84

Reproducible in:

slack_bolt==1.27.0
slack_sdk==3.40.1

The slack_bolt version

slack_bolt==1.27.0

Python runtime version

Python 3.12

OS info

Linux

Steps to reproduce:

  1. Create an Assistant with either the default store or a custom AssistantThreadContextStore
  2. Save thread context in thread_started using save_thread_context()
  3. Call get_thread_context() in user_message handler
from slack_bolt import App
from slack_bolt import Assistant, Say

assistant = Assistant()  # default store or custom — same result

@assistant.thread_started
def handle_thread_started(say, save_thread_context, payload):
    thread_context = payload["assistant_thread"]["context"]
    save_thread_context(thread_context)  # ✅ works, store.save() is called
    say("Hi!")

@assistant.thread_context_changed
def handle_context_changed(save_thread_context, payload):
    new_context = payload["assistant_thread"]["context"]
    save_thread_context(new_context)  # ✅ works

@assistant.user_message
def handle_user_message(say, get_thread_context):
    ctx = get_thread_context()  # ❌ always None, store.find() never called
    print(ctx)

app = App(token="xoxb-...", signing_secret="...")
app.use(assistant)
app.start(port=3000)

Expected result:

get_thread_context() should call store.find() and return the previously saved context (e.g., {"channel_id": "C0ADEEWTZPB", "team_id": "TU8CUBAPP", ...}).

Actual result:

get_thread_context() always returns None. The store's find() method is never invoked.

Root cause

In GetThreadContext.call(), the code checks if "assistant_thread" in self.payload before checking for channel/thread_ts:

if "assistant_thread" in self.payload:
    context = self.payload["assistant_thread"].get("context", {})
    if context.get("channel_id"):
        self._thread_context = AssistantThreadContext(context)
    self.thread_context_loaded = True

elif self.payload.get("channel") and self.payload.get("thread_ts"):
    self._thread_context = self.thread_context_store.find(
        channel_id=self.payload["channel"],
        thread_ts=self.payload["thread_ts"],
    )

In assistant_thread_started events, payload["assistant_thread"] contains a full context dict — this works fine.

However, in user_message events, payload["assistant_thread"] only contains an action_token:

{
  "assistant_thread": {
    "action_token": "10647138185092.960436384805.afce3599..."
  }
}

Because the key "assistant_thread" exists, the if branch is entered. There's no context inside, so _thread_context stays None. Then thread_context_loaded is set to True, and the elif branch that calls store.find() is never reached.

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

Activity

  1. self-assigned this
    on Mar 6, 2026
  2. srtaalej commented on Mar 6, 2026

    @srtaalej
    Contributor

    hi @jczhong84 thank you so much for reporting this! we've confirmed the bug and are working on a fix! 💟

  3. jczhong84 commented on Mar 6, 2026

    @jczhong84
    Author

    Hi @srtaalej thanks for the quick fix! I've another question around this context change. When it clicks into a thread of a channel, it will also trigger thread_context_changed event, although the channel is not changed. So I think either

    1. As the current context is about the channel id the user is viewing, can it only trigger the thread_context_changed when the channel changes?
    2. or can we add the thread id into the context as well as the channel id? actually this will be really helpful for the ai assistant, as it will know the exact thread context user is viewing. And we're thinking if there is any workaround to do this.
  4. rayterrill commented on Mar 9, 2026

    @rayterrill

    I just ran into this, spent so much time trying to figure out why this wasnt working until I found this.

    One thing I'm still confused by - if I want to use the default store, do I ever need to call save_thread_context or does that get handled automatically when both the @assistant.thread_started and @assistant.thread_context_changed events are fired - and I can just call get_thread_context when @assistant.user_message is called to grab the context?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions