.claude/skills/card-callback-performance/SKILL.md
Use when encountering IM platform card callback timeouts (error 200340), "card interaction timeout", "card page turn slow", "renderListCard slow", "renderDeleteModeCard slow", "ListSessions slow", "3 second timeout", "session list cache", "sessionListCache", "listSessionsCached", or when adding new card rendering code that calls expensive I/O operations (ListSessions, file scanning, JSONL parsing) in card callback paths.
npx skillsauth add liuyu520/cc-connect-fork card-callback-performanceInstall 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.
Document the 3-second timeout constraint on IM card interactions (Feishu/Lark error 200340) and the established caching pattern used to keep card rendering fast enough.
Feishu (and similar IM platforms) require card interaction callbacks to return
within 3 seconds. If the server doesn't respond in time, the platform returns
error code 200340 to the user.
Card interactions include:
act: prefixed callbackRegular slash commands (e.g., /list, /delete) do NOT have this constraint
— they can take as long as needed.
| File | Role |
|------|------|
| core/engine.go | sessionListCacheEntry type, listSessionsCached(), invalidateSessionListCache() |
| core/engine_cards.go | renderListCard(), renderListCardSafe(), renderDeleteModeCard(), renderDeleteModeSelectCard(), handleCardNav(), executeCardAction(), all render*Card() |
| core/engine_commands.go | cmdList(), cmdDelete() — slash command paths (no timeout constraint) |
| Function | File | Path Type | Constraint | Uses Cache? |
|----------|------|-----------|------------|-------------|
| renderListCard() | engine_cards.go | Card callback | 3s timeout | Yes |
| renderDeleteModeCard() | engine_cards.go | Card callback | 3s timeout | Yes |
| submitDeleteModeSelection() | engine_cards.go | Card callback | 3s timeout | No (mutates, then invalidates) |
| cmdList() | engine_commands.go | Slash command | No timeout | No (calls agent.ListSessions directly) |
| cmdDelete() | engine_commands.go | Slash command | No timeout | No |
// core/engine.go
// sessionListCacheEntry holds a cached ListSessions result with expiry.
type sessionListCacheEntry struct {
sessions []AgentSessionInfo
at time.Time
}
const sessionListCacheTTL = 30 * time.Second
// Engine struct fields:
sessionListCacheMu sync.RWMutex
sessionListCache map[Agent]*sessionListCacheEntry
func (e *Engine) listSessionsCached(agent Agent) ([]AgentSessionInfo, error) {
// 1. Read lock: check cache
e.sessionListCacheMu.RLock()
if entry, ok := e.sessionListCache[agent]; ok && time.Since(entry.at) < sessionListCacheTTL {
e.sessionListCacheMu.RUnlock()
return entry.sessions, nil
}
e.sessionListCacheMu.RUnlock()
// 2. Cache miss: call expensive I/O
sessions, err := agent.ListSessions(e.ctx)
if err != nil {
return nil, err
}
// 3. Write lock: store result
e.sessionListCacheMu.Lock()
if e.sessionListCache == nil {
e.sessionListCache = make(map[Agent]*sessionListCacheEntry)
}
e.sessionListCache[agent] = &sessionListCacheEntry{sessions: sessions, at: time.Now()}
e.sessionListCacheMu.Unlock()
return sessions, nil
}
func (e *Engine) invalidateSessionListCache(agent Agent) {
e.sessionListCacheMu.Lock()
delete(e.sessionListCache, agent)
e.sessionListCacheMu.Unlock()
}
Cache must be invalidated whenever session list changes:
| Location | File | Trigger |
|----------|------|---------|
| deleteSingleSessionReply() | engine_commands.go | After deleter.DeleteSession() succeeds |
| submitDeleteModeSelection() | engine_cards.go | After batch deletion completes |
| cmdNew() | engine_commands.go | After creating a new session |
Use listSessionsCached() instead of agent.ListSessions() when:
Use agent.ListSessions() directly when:
When adding a new card function that needs session data:
handleCardNav() or
executeCardAction() in engine_cards.goe.listSessionsCached(agent) instead of agent.ListSessions(e.ctx)e.invalidateSessionListCache(agent)
after the mutation| Symptom | Check |
|---------|-------|
| Error 200340 on page turn | renderListCard or renderDeleteModeCard in engine_cards.go taking >3s — check if listSessionsCached is being used |
| Stale session list after delete | invalidateSessionListCache not called after mutation |
| Cache never hits | TTL too short, or invalidateSessionListCache called too aggressively |
| Race condition in cache | Ensure sessionListCacheMu is used correctly (RLock for read, Lock for write) |
The same pattern can be applied to other card callbacks that do expensive I/O:
| Candidate | Function | File | Risk |
|-----------|----------|------|------|
| /history card | renderHistoryCard | engine_cards.go | Session history file reading |
| /doctor card | renderDoctorCard | engine_cards.go | CLI binary checks, version probing |
To add caching for a new operation:
<operation>Cached() + invalidate<Operation>Cache() methodsmessage-flow-architecture — How card actions reach the engineadd-new-feature — General feature addition workflowslash-command-system — How commands are dispatched (non-card paths)tools
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.