{"record":{"id":"2dfe581bb9b9a4f7","repo":"chenhg5/cc-connect","slug":"bridge-invalid-reply-context","errorCode":null,"errorMessage":"bridge: invalid reply context","messagePattern":"bridge: invalid reply context","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/bridge.go","lineNumber":472,"sourceCode":"\tdata, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"bridge: marshal reconstruct reply ctx: %w\", err)\n\t}\n\treturn string(data), nil\n}\n\nfunc bridgeTransportChatID(sessionKey string) (string, error) {\n\tparts := strings.SplitN(sessionKey, \":\", 3)\n\tif len(parts) < 2 || parts[1] == \"\" {\n\t\treturn \"\", fmt.Errorf(\"bridge: invalid session key %q\", sessionKey)\n\t}\n\treturn parts[1], nil\n}\n\nfunc (bp *BridgePlatform) SendCard(ctx context.Context, replyCtx any, card *Card) error {\n\trc, ok := replyCtx.(*bridgeReplyCtx)\n\tif !ok {\n\t\treturn fmt.Errorf(\"bridge: invalid reply context\")\n\t}\n\ta := bp.server.getAdapter(rc.Platform)\n\tif a == nil || !a.capabilities[\"card\"] {\n\t\treturn bp.Reply(ctx, replyCtx, card.RenderText())\n\t}\n\treturn bp.server.sendToAdapter(rc.Platform, map[string]any{\n\t\t\"type\":        \"card\",\n\t\t\"session_key\": rc.SessionKey,\n\t\t\"reply_ctx\":   rc.ReplyCtx,\n\t\t\"card\":        serializeCard(card),\n\t})\n}\n\nfunc (bp *BridgePlatform) ReplyCard(ctx context.Context, replyCtx any, card *Card) error {\n\treturn bp.SendCard(ctx, replyCtx, card)\n}\n\nfunc (bp *BridgePlatform) SendWithButtons(ctx context.Context, replyCtx any, content string, buttons [][]ButtonOption) error {","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/bridge.go#L454-L490","documentation":"BridgePlatform.SendCard asserts that replyCtx is a *bridgeReplyCtx before forwarding the card to the downstream adapter. Any other type (context from another platform, plain string ID, stale persisted value) yields this error. Note that unlike Reply, this path has no fallback rendering.","triggerScenarios":"Calling SendCard with a replyCtx not produced by this bridge — e.g. a context captured from a direct platform adapter, a JSON-decoded map, or a value created by a previous process.","commonSituations":"Queueing cards with serialized reply contexts and reusing them after restart; copy-pasting reply handling code between platform packages so the wrong context type is passed.","solutions":["Pass only contexts obtained from this BridgePlatform's reply/send calls or ReconstructReplyCtx.","If the context came from storage, reconstruct it via ReconstructReplyCtx(sessionKey) first.","Check the type at the call site (log %T) to confirm which code path produced the wrong value."],"exampleFix":"// before\nbp.SendCard(ctx, chatID, card) // chatID is a string, not a reply context\n\n// after\nrc, err := bp.ReconstructReplyCtx(sessionKey)\nif err != nil { return err }\nbp.SendCard(ctx, rc, card)","handlingStrategy":"type-guard","validationCode":"if _, ok := replyCtx.(*bridgeReplyCtx); !ok { return fmt.Errorf(\"SendCard needs a bridge reply context, got %T\", replyCtx) }","typeGuard":"func isBridgeReplyCtx(v any) bool { _, ok := v.(*bridgeReplyCtx); return ok }","tryCatchPattern":"if err := bp.SendCard(ctx, replyCtx, card); err != nil {\n    if strings.Contains(err.Error(), \"invalid reply context\") {\n        return bp.Reply(ctx, reconstructedCtx, card.RenderText())\n    }\n    return err\n}","preventionTips":["Centralize outgoing sends in one helper that owns a valid reply context.","Reconstruct contexts after restart instead of deserializing old ones.","Keep per-platform contexts in separate variables to avoid cross-wiring."],"tags":["go","type-assertion","cards"],"backgroundTag":"type-mismatch","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"}