chenhg5/cc-connect · error
qqbot: empty session key in reply context
Error message
qqbot: empty session key in reply context
What it means
SendWithButtons embeds the session key into every button's button_data so that button-click (INTERACTION_CREATE) callbacks can be routed back to the right session. If the reply context's sessionKey field is empty, the callback could not be routed, so the send is refused with this error (platform/qqbot/qqbot.go:435).
Source
Thrown at platform/qqbot/qqbot.go:435
return p.apiRequest("POST", url, body)
}
var _ core.FileSender = (*Platform)(nil)
var _ core.InlineButtonSender = (*Platform)(nil)
// SendWithButtons sends a message with QQ Bot inline keyboard buttons.
// Implements core.InlineButtonSender.
func (p *Platform) SendWithButtons(ctx context.Context, replyCtx any, content string, buttons [][]core.ButtonOption) error {
rctx, ok := replyCtx.(*replyContext)
if !ok {
return fmt.Errorf("qqbot: SendWithButtons: invalid reply context type %T", replyCtx)
}
// Use session key from replyContext to embed in button_data
sessionKey := rctx.sessionKey
if sessionKey == "" {
return fmt.Errorf("qqbot: empty session key in reply context")
}
// Build QQ Bot keyboard rows from button options
var rows []map[string]any
for i, row := range buttons {
var btns []map[string]any
for j, btn := range row {
// Encode decision + session key into button_data so we can route
// the INTERACTION_CREATE event back to the right session.
// btn.Data is already "perm:allow", "perm:deny", or "perm:allow_all"
buttonData := btn.Data + ":" + sessionKey
btnID := fmt.Sprintf("b_%d_%d", i, j)
visitedLabel := "已操作"
style := 1 // blue
if strings.Contains(btn.Data, "deny") {
visitedLabel = "已拒绝"
style = 0 // greyView on GitHub (pinned to 4000b2338a)
Solutions
- Always build reply contexts via the qqbot platform's normal message handling or ReconstructReplyCtx, both of which set sessionKey.
- If constructing manually, set sessionKey to the full key (e.g. "qqbot:g:<group_openid>").
- Audit recent changes to where replyContext is created in qqbot.go for a dropped sessionKey assignment.
Example fix
// before
rctx := &replyContext{messageType: "group", groupOpenID: gid}
sendWithButtons(rctx)
// after
rctx := &replyContext{messageType: "group", groupOpenID: gid, sessionKey: "qqbot:g:" + gid}
sendWithButtons(rctx) Defensive patterns
Strategy: validation
Validate before calling
if rc, ok := replyCtx.(*replyContext); !ok || rc.sessionKey == "" {
return fmt.Errorf("reply context missing session key; cannot route button callbacks")
} Type guard
rc, ok := replyCtx.(*replyContext); ok && rc.sessionKey != ""
Prevention
- Always create reply contexts through code paths that set sessionKey (event handler or ReconstructReplyCtx).
- Add an invariant check/test asserting sessionKey is non-empty wherever replyContext is constructed.
- Do not cache/deserialize reply contexts in ways that drop fields.
When it happens
Trigger: Calling SendWithButtons with a *replyContext that was built without setting sessionKey — e.g. a manually constructed context, or code paths that populate messageType/openIDs but skip sessionKey.
Common situations: Hand-constructed replyContext in tests or plugins; a code change that stopped copying sessionKey when creating reply contexts; deserialized/cached contexts missing the field.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- qqbot: unknown message type %q
- qqbot: SendWithButtons: invalid reply context type %T
- bridge: invalid reply context type %T
- reconstruct reply ctx: %w
- cloud_web: reconstruct_reply not supported by gateway
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/869e95a13a2d92d0.
Report an issue: GitHub.