{"record":{"id":"9d3435fdb48e6e87","repo":"chenhg5/cc-connect","slug":"s-streamrichcardtext-invalid-preview-handle-typ","errorCode":null,"errorMessage":"%s: StreamRichCardText: invalid preview handle type %T","messagePattern":"(.+?): StreamRichCardText: invalid preview handle type %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/feishu/feishu.go","lineNumber":5209,"sourceCode":"\t}\n\tif resp.Data.CardID == \"\" {\n\t\treturn \"\", fmt.Errorf(\"%s: create card entity: empty card_id in response\", p.tag())\n\t}\n\treturn resp.Data.CardID, nil\n}\n\n// StreamRichCardText implements core.RichCardTextStreamer. Pushes the latest\n// fullText to the rich card's main_text element via cardkit-v1 streaming text\n// update API. The Lark client renders the increment between consecutive PUTs\n// with a typewriter animation (controlled by the card's streaming_config).\n//\n// Returns ErrNotSupported when the handle has no cardID (preview was created\n// via the inline-card-JSON fallback path; engine should fall back to full-card\n// Patch).\nfunc (p *Platform) StreamRichCardText(ctx context.Context, previewHandle any, fullText string) error {\n\th, ok := previewHandle.(*feishuPreviewHandle)\n\tif !ok {\n\t\treturn fmt.Errorf(\"%s: StreamRichCardText: invalid preview handle type %T\", p.tag(), previewHandle)\n\t}\n\n\t// Serialize all PUTs for one card so the monotonic sequence counter is\n\t// preserved across concurrent EventText calls; rate-limit headroom is\n\t// huge (Lark allows 50 QPS per element).\n\th.mu.Lock()\n\tdefer h.mu.Unlock()\n\n\tif h.cardID == \"\" {\n\t\treturn core.ErrNotSupported\n\t}\n\n\th.sequence++\n\tapiPath := fmt.Sprintf(\"/open-apis/cardkit/v1/cards/%s/elements/%s/content\",\n\t\th.cardID, richCardMainTextElementID)\n\tbody := map[string]any{\n\t\t\"content\":  fullText,\n\t\t\"sequence\": h.sequence,","sourceCodeStart":5191,"sourceCodeEnd":5227,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/feishu/feishu.go#L5191-L5227","documentation":"StreamRichCardText implements core.RichCardTextStreamer and accepts the preview handle as `any`. If the caller passes anything that is not a *feishuPreviewHandle (e.g. a handle from a different platform or a nil/wrong type), the type assertion fails and this error is thrown. It is a programming/contract error, not an API error.","triggerScenarios":"Calling StreamRichCardText with a handle obtained from another platform adapter, a value (non-pointer) feishuPreviewHandle, a nil interface, or a handle from the inline-card-JSON fallback path that was stored with the wrong concrete type.","commonSituations":"Engine wired up to a mixed set of platforms and passes one platform's handle to another; refactoring changed the handle type; test code constructing ad-hoc handles.","solutions":["Only pass the exact handle returned by the same Feishu platform's SendPreview/preview-creation call","Check core.ErrNotSupported separately: a feishu handle with empty CardID legitimately returns ErrNotSupported, not this error","Fix engine dispatch so handles are always paired with the platform that created them","In tests, construct handles as *feishuPreviewHandle"],"exampleFix":"// before\nerr := platform.StreamRichCardText(ctx, someOtherHandle, text)\n// after\nif h, ok := someOtherHandle.(*feishu.PreviewHandle); ok {\n\terr = platform.StreamRichCardText(ctx, h, text)\n} else {\n\terr = core.ErrNotSupported // or fall back to full-card Patch\n}","handlingStrategy":"type-guard","validationCode":"if _, ok := previewHandle.(*feishuPreviewHandle); !ok {\n\t// do not call StreamRichCardText; use full-card Patch instead\n}","typeGuard":"func asFeishuHandle(h any) (*feishuPreviewHandle, bool) {\n\tp, ok := h.(*feishuPreviewHandle)\n\treturn p, ok\n}","tryCatchPattern":"if err := p.StreamRichCardText(ctx, handle, text); err != nil {\n\tif errors.Is(err, core.ErrNotSupported) || strings.Contains(err.Error(), \"invalid preview handle type\") {\n\t\t// fall back to full-card Patch\n\t}\n}","preventionTips":["Only pass handles returned by the same platform instance","Keep handle pairing explicit in the engine (handle → creating platform)","Always use pointer handles (*feishuPreviewHandle), never values","Add an engine-level capability check (RichCardTextStreamer + valid handle) before calling"],"tags":["feishu","type-assertion","contract","streaming-card"],"backgroundTag":"incompatible-source-type","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"}