{"record":{"id":"a834b2560fe9f1f2","repo":"chenhg5/cc-connect","slug":"workspace-binding-unavailable-for-source-channel","errorCode":null,"errorMessage":"workspace binding unavailable for source channel %q","messagePattern":"workspace binding unavailable for source channel %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/engine.go","lineNumber":15944,"sourceCode":"func (e *Engine) relayContextForSourceSessionKey(fromProject, sourceSessionKey string) (Agent, *SessionManager, string, error) {\n\tplatformName, chatID, err := parseSessionKeyParts(sourceSessionKey)\n\tif err != nil {\n\t\treturn nil, nil, \"\", fmt.Errorf(\"invalid source session key: %w\", err)\n\t}\n\n\trelaySessionKey := relayConversationKey(fromProject, platformName, chatID)\n\tif !e.multiWorkspace || e.workspaceBindings == nil {\n\t\treturn e.agent, e.sessions, relaySessionKey, nil\n\t}\n\n\tchannelKey := workspaceChannelKey(platformName, chatID)\n\tworkspace, _, err := e.resolveWorkspace(e.platformForName(platformName), chatID)\n\tif err != nil {\n\t\treturn nil, nil, \"\", fmt.Errorf(\"resolve relay workspace: %w\", err)\n\t}\n\tif workspace == \"\" {\n\t\tif b, _, usable := e.lookupEffectiveWorkspaceBinding(channelKey); b != nil && !usable {\n\t\t\treturn nil, nil, \"\", fmt.Errorf(\"workspace binding unavailable for source channel %q\", channelKey)\n\t\t}\n\t\treturn nil, nil, \"\", fmt.Errorf(\"no workspace binding for source channel %q\", channelKey)\n\t}\n\n\tagent, sessions, err := e.getOrCreateWorkspaceAgent(workspace)\n\tif err != nil {\n\t\treturn nil, nil, \"\", fmt.Errorf(\"get relay workspace agent: %w\", err)\n\t}\n\tif ws := e.workspacePool.Get(workspace); ws != nil {\n\t\tws.Touch()\n\t}\n\treturn agent, sessions, relaySessionKey, nil\n}\n\n// HandleRelay processes a relay message synchronously: starts or resumes a\n// dedicated relay session, sends the message to the agent, and blocks until\n// the complete response is collected (or the relay context times out).\nfunc (e *Engine) HandleRelay(ctx context.Context, fromProject, sourceSessionKey, message string) (string, error) {","sourceCodeStart":15926,"sourceCodeEnd":15962,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/engine.go#L15926-L15962","documentation":"In multi-workspace relay mode, resolveWorkspace returned an empty workspace string AND an effective workspace binding exists for the channel but is currently unusable (e.g. its directory is missing or the binding is disabled). cc-connect reports 'workspace binding unavailable for source channel %q' with the channel key. This distinguishes 'binding exists but broken' from 'no binding at all' (the sibling error).","triggerScenarios":"Relay targets a channel that HAS a configured workspace binding (lookupEffectiveWorkspaceBinding returns b != nil) but the binding is not usable — commonly because the bound workspace path no longer exists on disk, lacks permissions, or the binding was toggled off.","commonSituations":"Workspace directory deleted/renamed/moved after binding was configured; insufficient filesystem permissions after running under a different user; binding defined for a channel the workspace no longer serves.","solutions":["Check the binding for the printed channel key in config.toml / workspace bindings store","Verify the bound workspace path exists and is readable/writable by the cc-connect process","Re-bind the channel to a valid workspace or remove the stale binding","Check process user/permissions if the path exists but is inaccessible"],"exampleFix":"// before (config.toml)\n[[workspace_bindings]]\nchannel = \"telegram:12345678\"\npath = \"/home/user/old-project\"   # deleted\n// after\n[[workspace_bindings]]\nchannel = \"telegram:12345678\"\npath = \"/home/user/projects/active\"","handlingStrategy":"validation","validationCode":"// validate bindings at startup: path must exist and be writable\nfor _, b := range bindings {\n    if fi, err := os.Stat(b.Path); err != nil || !fi.IsDir() {\n        slog.Error(\"workspace binding path invalid\", \"channel\", b.Channel, \"path\", b.Path)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"workspace binding unavailable\") {\n        slog.Error(\"binding exists but unusable; check bound path\", \"channel\", channelKey)\n    }\n    return err\n}","preventionTips":["Add a startup check that every bound workspace path exists and is writable","Use stable absolute paths (or symlinks you control) for workspace bindings","Re-validate bindings after moving/deleting project directories","Run cc-connect under a user with consistent filesystem permissions"],"tags":["workspace","binding","relay","configuration"],"backgroundTag":"invalid-config-value","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"}