.claude/skills/session-key-architecture/SKILL.md
This skill should be used when the user asks about "session key", "SessionKey", "thread isolation", "thread_isolation", "share_session_in_channel", "BaseSessionKeyer", "base session key", "session binding", "different threads different sessions", "session key format", "per-user session", "per-channel session", or needs to debug, extend, or understand how session keys are constructed across platforms and how they affect routing, project binding, and conversation isolation.
npx skillsauth add liuyu520/cc-connect-fork session-key-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Session keys are the primary mechanism for mapping incoming messages to conversations, agent sessions, and project bindings. Each platform constructs session keys differently based on its chat model. Understanding session key formats is critical when debugging routing issues, implementing thread isolation, or extending the ProjectRouter binding system.
A session key is a string that uniquely identifies a "conversation context." All cc-connect subsystems use it:
| Subsystem | Usage |
|-----------|-------|
| Engine.handleMessage() | Maps message to interactive state / agent session |
| SessionManager | Persists session history per session key |
| ProjectRouter | Binds session key → project name |
| Rate limiter | Tracks per-session message counts |
| Platform | Default Format | share_session | thread_isolation |
|----------|---------------|---------------|------------------|
| Feishu/Lark | feishu:{chatID}:{userID} | feishu:{chatID} | feishu:{chatID}:root:{rootMsgID} |
| Telegram | telegram:{chatID}:{userID} | telegram:{chatID} | N/A |
| Discord | discord:{channelID}:{userID} | discord:{channelID} | discord:{threadID} |
| Slack | slack:{channel}:{user} | slack:{channel} | N/A |
| DingTalk | dingtalk:{convID}:{staffID} | dingtalk:{convID} | N/A |
| WeChat Work | wecom:{userID} | N/A | N/A |
| QQ (OneBot) | qq:{groupID}:{userID} / qq:{userID} | qq:g:{groupID} | N/A |
| LINE | line:{targetID} | N/A | N/A |
For the complete format details with code references, consult
references/platform-session-keys.md.
When thread_isolation is enabled (Feishu, Discord), each top-level message
creates a unique session key containing the root message ID. This causes:
/project switches to apply only to the current threadBaseSessionKeyer is an optional interface in core/interfaces.go:
type BaseSessionKeyer interface {
BaseSessionKey(msg *Message) string
}
Platforms implement this to return a broader "user-in-chat" level key, stripping thread-specific components. ProjectRouter uses it for fallback binding lookup.
1. bindings[msg.SessionKey] → exact match (per-thread)
2. bindings[baseSessionKey(msg)] → fallback (per-user-in-chat)
3. No binding found → show project selection
When a project is selected or switched, setBinding() stores both:
bindings[sessionKey] = project — exact thread bindingbindings[baseKey] = project — broader fallback bindingThis ensures new threads inherit the most recent project selection.
// BaseSessionKey derives user-in-chat key from thread-isolated key.
// "feishu:chatId:root:msgId" → "feishu:chatId:userId"
func (p *Platform) BaseSessionKey(msg *core.Message) string {
if !p.threadIsolation { return msg.SessionKey }
parts := strings.SplitN(msg.SessionKey, ":", 3)
if len(parts) < 3 { return msg.SessionKey }
if _, ok := parseThreadRootID(parts[2]); !ok { return msg.SessionKey }
return fmt.Sprintf("%s:%s:%s", p.tag(), parts[1], msg.UserID)
}
| File | Role |
|------|------|
| core/interfaces.go | BaseSessionKeyer interface definition |
| core/project_router.go | baseSessionKey(), setBinding(), fallback lookup |
| platform/feishu/feishu.go | makeSessionKey(), BaseSessionKey(), isThreadSessionKey() |
| platform/discord/discord.go | Discord session key construction |
BaseSessionKey(msg *Message) string on the Platform structmsg.SessionKey unchanged when thread isolation is not active# Session key related tests
go test ./core/ -v -run "TestProjectRouter_BaseSessionKey"
go test ./platform/feishu/ -v -run "TestLark_SessionKey|TestLark_ThreadIsolation"
Test stubs for BaseSessionKeyer:
type stubBaseSessionKeyPlatform struct {
stubRouterPlatform
baseKeyFunc func(msg *Message) string
}
func (p *stubBaseSessionKeyPlatform) BaseSessionKey(msg *Message) string { ... }
Common symptoms and diagnosis:
| Symptom | Likely Cause |
|---------|-------------|
| /project switch doesn't persist to new messages | thread_isolation + missing BaseSessionKeyer |
| Different users share the same session | share_session_in_channel enabled |
| Reply in thread creates new session | thread_isolation splits per-root-message |
| Project selection keeps appearing | No binding for this session key + no base key fallback |
Diagnostic steps:
session= field to see the actual session key formatthread_isolation, share_session_in_channel){dataDir}/project_bindings_*.jsonreferences/platform-session-keys.md — Complete session key construction code for each platform with line referencesmessage-flow-architecture — How session keys feed into the Engine message processing pipelineproject-router — How session keys map to project bindingstools
This skill should be used when the user asks about "webui", "web ui", "vibe coding", "WebUIServer", "web interface", "browser Claude Code", "web frontend", "React admin dashboard", "web/src", "VibeCoding page", "WebSocket /api/vibe/ws", "webui config", "port 9830", "static file serving", "webuiSession", "claude code subprocess from web", "project dropdown", "work dir select", "listProjects", "Management API frontend", "multi tab vibe", "TabBar", "VibeSession component", "copy work dir", "clipboard copy", "disconnect confirm", "断开确认", "复制路径", "copyWorkDir", "AgentSystemPrompt", "project awareness", "/project command in agent", "attachment upload", "sendWithAttachments", "file upload vibe", "image upload vibe", "drag drop vibe", "paste image vibe", or needs to debug, extend, or understand the browser-based Vibe Coding interface and its Go backend.
tools
This skill should be used when the user asks about "permission request", "control_request", "control_response", "permission prompt tool", "permission-prompt-tool stdio", "permission popup not showing", "permission dialog missing", "authorize tool use", "allow deny button", "webuiSession permission", "respondPermission", "updatedInput", "control_cancel_request", "permission cancelled", "permission_cancelled", "pendingInputs", "permission flow webui", "vibe permission", "前端没有弹出授权", "权限请求不显示", "权限弹窗", "webuiSession vs claudeSession", "webui parity", or needs to debug, extend, or understand how tool permission requests flow between Claude Code CLI, the Go WebUI backend, and the React frontend.
tools
This skill should be used when the user asks about "export markdown", "export chat", "download markdown", "export conversation", "导出 Markdown", "导出聊天记录", "ExportRequest", "ExportMessage", "handleVibeExportMarkdown", "buildExportMarkdown", "POST /api/vibe/export", "exportVibeSession", "chat export", "markdown download", "sanitizeFilename", "Content-Disposition attachment", "export button vibe", "history export", "tool_use in export", "message type mapping export", or needs to debug, extend, or understand the chat history Markdown export feature including the full-stack data flow from frontend trigger to file download.
tools
This skill should be used when the user asks about "attachment upload", "file upload", "image upload", "paste image", "drag drop file", "vibe attachment", "pendingAttachments", "sendWithAttachments", "fileToAttachment", "AttachmentItem", "base64 attachment", "WebSocket attachment protocol", "multimodal content", "image content block", "file content block", "clipboard paste image", "drag and drop upload", "attachment preview", "file size limit", "10MB limit", "ExtFromMime", "附件上传", "图片上传", "拖拽上传", "粘贴图片", "附件预览", "文件大小限制", or needs to debug, extend, or understand the WebUI attachment upload feature including the full-stack data flow from browser to Claude Code CLI.