Skip to content

SDK drift: WS subscribe()'s dedup key (subKey/sub_key) diverges for a None/falsy-but-present first argument, independent of the symbols list divergence in #1498 #2161

Description

@realfishsam

Drift

TypeScript's subscribe() computes firstArg once via nullish-coalescing (args[0] ?? "") and reuses it for both the subscription-reuse dedup key (subKey) and the symbols metadata. Python's subscribe() independently recomputes the dedup key from a different truthiness check — args[0] if args else "", i.e. "is the whole args list non-empty," not "is args[0] itself present/truthy." For args = [None], this produces a different subKey/sub_key between the two SDKs, which changes whether a call is treated as reusing an existing subscription or opening a new one. This is distinct from #1498, which is scoped to the symbols metadata field derivation and does not address the dedup key computation or its consequence for subscription reuse.

TypeScript SDK

sdks/typescript/pmxt/ws-client.ts:348-351:

const firstArg = args[0] ?? "";
const subKey = Array.isArray(firstArg)
    ? `${method}:${[...firstArg].sort().join(",")}`
    : `${method}:${firstArg}`;

For args = [null]: args[0] ?? "" evaluates null ?? ""firstArg = "", so subKey = "method:".

Python SDK

sdks/python/pmxt/ws_client.py:305-309:

first_arg = args[0] if args else ""
if isinstance(first_arg, list):
    sub_key = f"{method}:{','.join(sorted(first_arg))}"
else:
    sub_key = f"{method}:{first_arg}"

For args = [None]: args is a non-empty list (truthy), so first_arg = args[0] = None (the if args else "" only checks the list itself, not the element) → sub_key = f"{method}:{first_arg}" = "method:None".

Expected

The same args input should produce the same dedup key in both SDKs, so that whether a given subscribe() call reuses an active subscription or starts a new one is consistent across languages. Both should base the fallback on args[0]'s own truthiness (matching TypeScript's ??), not on whether args as a whole is non-empty.

Impact

For a call shaped like subscribe(exchange, method, [None], ...), TypeScript computes dedup key "method:" while Python computes "method:None" — a different key means the two SDKs disagree on whether a second such call reuses the first subscription (returning cached/pending data) or opens a fresh one, changing observable subscribe-reuse behavior, not just metadata shape.


Found by automated SDK cross-language drift audit

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions