Repository navigation
GetThreadContext returns None in user_message handler #1452
Description
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:
- Create an Assistant with either the default store or a custom AssistantThreadContextStore
- Save thread context in thread_started using save_thread_context()
- 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
srtaalej commentedon Mar 6, 2026
hi @jczhong84 thank you so much for reporting this! we've confirmed the bug and are working on a fix! 💟
jczhong84 commentedon Mar 6, 2026
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_changedevent, although the channel is not changed. So I think eitherthread_context_changedwhen the channel changes?rayterrill commentedon Mar 9, 2026
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_contextor does that get handled automatically when both the@assistant.thread_startedand@assistant.thread_context_changedevents are fired - and I can just callget_thread_contextwhen@assistant.user_messageis called to grab the context?