{"record":{"id":"869e95a13a2d92d0","repo":"chenhg5/cc-connect","slug":"qqbot-empty-session-key-in-reply-context","errorCode":null,"errorMessage":"qqbot: empty session key in reply context","messagePattern":"qqbot: empty session key in reply context","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/qqbot/qqbot.go","lineNumber":435,"sourceCode":"\n\treturn p.apiRequest(\"POST\", url, body)\n}\n\nvar _ core.FileSender = (*Platform)(nil)\nvar _ core.InlineButtonSender = (*Platform)(nil)\n\n// SendWithButtons sends a message with QQ Bot inline keyboard buttons.\n// Implements core.InlineButtonSender.\nfunc (p *Platform) SendWithButtons(ctx context.Context, replyCtx any, content string, buttons [][]core.ButtonOption) error {\n\trctx, ok := replyCtx.(*replyContext)\n\tif !ok {\n\t\treturn fmt.Errorf(\"qqbot: SendWithButtons: invalid reply context type %T\", replyCtx)\n\t}\n\n\t// Use session key from replyContext to embed in button_data\n\tsessionKey := rctx.sessionKey\n\tif sessionKey == \"\" {\n\t\treturn fmt.Errorf(\"qqbot: empty session key in reply context\")\n\t}\n\n\t// Build QQ Bot keyboard rows from button options\n\tvar rows []map[string]any\n\tfor i, row := range buttons {\n\t\tvar btns []map[string]any\n\t\tfor j, btn := range row {\n\t\t\t// Encode decision + session key into button_data so we can route\n\t\t\t// the INTERACTION_CREATE event back to the right session.\n\t\t\t// btn.Data is already \"perm:allow\", \"perm:deny\", or \"perm:allow_all\"\n\t\t\tbuttonData := btn.Data + \":\" + sessionKey\n\n\t\t\tbtnID := fmt.Sprintf(\"b_%d_%d\", i, j)\n\t\t\tvisitedLabel := \"已操作\"\n\t\t\tstyle := 1 // blue\n\t\t\tif strings.Contains(btn.Data, \"deny\") {\n\t\t\t\tvisitedLabel = \"已拒绝\"\n\t\t\t\tstyle = 0 // grey","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/qqbot/qqbot.go#L417-L453","documentation":"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).","triggerScenarios":"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.","commonSituations":"Hand-constructed replyContext in tests or plugins; a code change that stopped copying sessionKey when creating reply contexts; deserialized/cached contexts missing the field.","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."],"exampleFix":"// before\nrctx := &replyContext{messageType: \"group\", groupOpenID: gid}\nsendWithButtons(rctx)\n// after\nrctx := &replyContext{messageType: \"group\", groupOpenID: gid, sessionKey: \"qqbot:g:\" + gid}\nsendWithButtons(rctx)","handlingStrategy":"validation","validationCode":"if rc, ok := replyCtx.(*replyContext); !ok || rc.sessionKey == \"\" {\n    return fmt.Errorf(\"reply context missing session key; cannot route button callbacks\")\n}","typeGuard":"rc, ok := replyCtx.(*replyContext); ok && rc.sessionKey != \"\"","tryCatchPattern":null,"preventionTips":["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."],"tags":["qqbot","missing-required-field","reply-context"],"backgroundTag":"empty-required-field","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}