{"record":{"id":"eeb7f3b5b4b7ab98","repo":"chenhg5/cc-connect","slug":"relay-no-binding-for-this-chat-use-bind-projec","errorCode":null,"errorMessage":"relay: no binding for this chat. Use /bind <project> first","messagePattern":"relay: no binding for this chat\\. Use /bind <project> first","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/relay.go","lineNumber":219,"sourceCode":"\tResponse string `json:\"response\"`\n}\n\n// Send delivers a message from one bot to another and returns the response.\nfunc (rm *RelayManager) Send(ctx context.Context, req RelayRequest) (*RelayResponse, error) {\n\tplatform, chatID, err := parseSessionKeyParts(req.SessionKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"relay: invalid session key: %w\", err)\n\t}\n\n\trm.mu.RLock()\n\tbinding := rm.bindings[chatID]\n\ttargetEngine := rm.engines[req.To]\n\tsourceEngine := rm.engines[req.From]\n\tvisibility := rm.visibility\n\trm.mu.RUnlock()\n\n\tif binding == nil {\n\t\treturn nil, fmt.Errorf(\"relay: no binding for this chat. Use /bind <project> first\")\n\t}\n\tif _, ok := binding.Bots[req.To]; !ok {\n\t\tvar bound []string\n\t\tfor proj := range binding.Bots {\n\t\t\tif proj != req.From {\n\t\t\t\tbound = append(bound, proj)\n\t\t\t}\n\t\t}\n\t\treturn nil, fmt.Errorf(\"relay: project %q is not bound in this chat. Available targets: %s (use the exact name)\", req.To, strings.Join(bound, \", \"))\n\t}\n\tif targetEngine == nil {\n\t\treturn nil, fmt.Errorf(\"relay: target engine %q not found (is the project running?)\", req.To)\n\t}\n\n\tfromName := req.From\n\tif binding.Bots[req.From] != \"\" {\n\t\tfromName = binding.Bots[req.From]\n\t}","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/relay.go#L201-L237","documentation":"RelayManager.Send in core/relay.go throws this when the chat the request originates from has no relay binding registered in rm.bindings. A binding is created only after a user runs /bind in that chat, mapping the chat to one or more project bots. Without it, the relay manager refuses to route cross-project messages for safety.","triggerScenarios":"Calling rm.Send(ctx, RelayRequest{From, To, SessionKey, Message}) where parseSessionKeyParts(SessionKey) yields a chatID that is not a key in rm.bindings — i.e. /bind was never run in that chat, or the SessionKey's chatID differs from the chat where /bind was executed.","commonSituations":"Users send relay commands (/to <project> <msg>) in a group or DM before ever running /bind; the bot moved to a new chat (new chatID) after a group migration; the session key passed programmatically has a chatID that doesn't match the bound chat.","solutions":["Run /bind <project> in the chat that is issuing the relay request, then retry","Verify the SessionKey's chatID (via parseSessionKeyParts: 'platform:chatID:userID' or 'relay:sourceProject:chatID') matches the chat where the binding exists","Re-register the binding programmatically if driving RelayManager from code (populate rm.bindings[chatID])","If the chat was migrated/recreated, re-run /bind since the old chatID no longer matches"],"exampleFix":"// before: relay sent from an unbound chat\nrm.Send(ctx, core.RelayRequest{From: \"proj-a\", To: \"proj-b\", SessionKey: \"feishu:oc_newchat:u1\", Message: \"hi\"})\n// after: run \"/bind proj-b\" in chat oc_newchat first, or use the bound chat's key\nrm.Send(ctx, core.RelayRequest{From: \"proj-a\", To: \"proj-b\", SessionKey: \"feishu:oc_boundchat:u1\", Message: \"hi\"})","handlingStrategy":"validation","validationCode":"func chatBound(rm *core.RelayManager, sessionKey string) bool {\n\t_, chatID, err := core.ParseSessionKeyParts(sessionKey) // or replicate SplitN(\":\", 3)\n\tif err != nil { return false }\n\trm.Lock(); defer rm.Unlock()\n\treturn rm.Bindings()[chatID] != nil\n}\n// only call Send when chatBound(...) is true; otherwise prompt the user to run /bind","typeGuard":"if binding := rm.LookupBinding(chatID); binding == nil { return fmt.Errorf(\"chat %s not bound; run /bind first\", chatID) }","tryCatchPattern":"resp, err := rm.Send(ctx, req)\nif err != nil {\n\tif strings.Contains(err.Error(), \"no binding for this chat\") {\n\t\treply(\"This chat is not bound. Use /bind <project> first.\")\n\t\treturn\n\t}\n\treturn err\n}","preventionTips":["Run /bind in every chat before issuing relay commands","Keep the session key's chatID stable — re-run /bind after group migration or bot re-invite","In code, always derive SessionKey from the incoming message context, never hand-build it"],"tags":["relay","binding","chat","configuration"],"backgroundTag":"missing-required-config","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}