{"record":{"id":"35397329d45cce3f","repo":"Tencent/WeKnora","slug":"parse-qqbot-credentials-w","errorCode":null,"errorMessage":"parse qqbot credentials: %w","messagePattern":"parse qqbot credentials: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/qqbot/factory.go","lineNumber":15,"sourceCode":"package qqbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/Tencent/WeKnora/internal/im\"\n\t\"github.com/Tencent/WeKnora/internal/logger\"\n)\n\nfunc NewFactory() im.AdapterFactory {\n\treturn func(factoryCtx context.Context, channel *im.IMChannel, msgHandler func(context.Context, *im.IncomingMessage) error) (im.Adapter, context.CancelFunc, error) {\n\t\tcreds, err := im.ParseCredentials(channel.Credentials)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"parse qqbot credentials: %w\", err)\n\t\t}\n\n\t\tmode := im.ResolveMode(channel, \"websocket\")\n\t\tif mode != \"websocket\" {\n\t\t\treturn nil, nil, fmt.Errorf(\"unsupported qqbot mode: %s (only websocket is supported)\", mode)\n\t\t}\n\n\t\tclient, err := NewClient(\n\t\t\tim.GetString(creds, \"app_id\"),\n\t\t\tim.GetString(creds, \"client_secret\"),\n\t\t\tim.GetString(creds, \"api_base_url\"),\n\t\t\tim.GetString(creds, \"gateway_url\"),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tlongConn := NewLongConnClient(client, msgHandler)","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/qqbot/factory.go#L1-L33","documentation":"The QQ bot adapter factory parses channel.Credentials into required fields (appId, clientSecret, etc.) and wraps any parse failure with this error. It fires when credentials are missing, malformed JSON, or lacking required keys.","triggerScenarios":"An IM channel of type qqbot is created whose Credentials string is not valid JSON or is missing required keys, and a websocket adapter is being constructed via NewFactory.","commonSituations":"Credentials pasted as YAML/INI instead of JSON; typo'd key names; credentials lost during environment provisioning; channel created before secrets were injected.","solutions":["Set channel.Credentials to JSON containing the required keys (appId, clientSecret)","Validate the credentials JSON with im.ParseCredentials in a startup check","Re-create or update the IM channel record with correctly serialized JSON"],"exampleFix":"// before\nchannel.Credentials = \"appId=123 secret=abc\"\n// after\nchannel.Credentials = `{\"appId\":\"123\",\"clientSecret\":\"abc\"}`","handlingStrategy":"validation","validationCode":"func validateQQBotCreds(raw string) error {\n    var m map[string]string\n    if err := json.Unmarshal([]byte(raw), &m); err != nil {\n        return fmt.Errorf(\"credentials must be JSON: %w\", err)\n    }\n    for _, k := range []string{\"appId\", \"clientSecret\"} {\n        if m[k] == \"\" { return fmt.Errorf(\"missing credential key: %s\", k) }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"adapter, cancel, err := factory(ctx, channel, handler)\nif err != nil && strings.Contains(err.Error(), \"parse qqbot credentials\") {\n    return fmt.Errorf(\"channel credentials malformed: %w\", err)\n}","preventionTips":["Store channel credentials as JSON with exactly the expected keys","Run ParseCredentials in a startup config check","Keep secrets in a manager and serialize to JSON consistently","Test adapter creation for each channel on deploy"],"tags":["configuration","credentials","qqbot","json"],"backgroundTag":"invalid-api-credentials","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}