Skip to content

SDK drift: fromServerError double-prefixes not-found messages reconstructed from a server payload; Python's from_server_error guards against it #2159

Description

@realfishsam

Drift

When a server error payload for an ORDER_NOT_FOUND/MARKET_NOT_FOUND/EVENT_NOT_FOUND code already contains a fully-formatted message (e.g. "Order not found: 123"), TypeScript's fromServerError passes that message straight into the matching subclass's constructor, which treats its first argument as a bare identifier and unconditionally re-wraps it with the same prefix — producing a doubled message like "Order not found: Order not found: 123". Python's from_server_error reconstructs the message through the same path but explicitly guards against this via _format_not_found_message, which is a no-op if the identifier already starts with the target prefix.

TypeScript SDK

sdks/typescript/pmxt/errors.ts:165-166 (fromServerError, generic branch): for any mapped error class other than RateLimitExceeded/ValidationError, it calls:

return new ErrorClass(message, exchange);

For code === "ORDER_NOT_FOUND", ErrorClass is OrderNotFound (errors.ts:57-61):

export class OrderNotFound extends NotFoundError {
    constructor(orderId: string, exchange?: string) {
        super(`Order not found: ${orderId}`, exchange, "ORDER_NOT_FOUND");
    }
}

message (the already-formatted server string) is passed positionally as orderId and unconditionally re-wrapped in `Order not found: ${orderId}`. The same pattern exists for MarketNotFound (errors.ts:63-67) and EventNotFound (errors.ts:69-73) — none guard against an already-prefixed input.

Python SDK

sdks/python/pmxt/errors.py:169 (from_server_error) calls error_class(message, **kwargs) the same way, landing on OrderNotFound.__init__ (errors.py:58-63):

def __init__(self, order_id: str, exchange: str | None = None, **_ignored: Any):
    super().__init__(
        _format_not_found_message("Order not found: ", order_id),
        code="ORDER_NOT_FOUND",
        exchange=exchange,
    )

_format_not_found_message (errors.py:32-33) checks identifier.startswith(prefix) and returns it unchanged if so, avoiding the double prefix. MarketNotFound (errors.py:66-73) and EventNotFound (errors.py:76-83) use the same guarded helper.

Expected

A server error payload like {"code": "ORDER_NOT_FOUND", "message": "Order not found: 123"} round-tripped through fromServerError/from_server_error should produce the same .message in both SDKs. Either both should guard against re-prefixing (matching Python's current behavior), or neither should.

Impact

Any hosted-mode or sidecar error response for a not-found condition gets its message doubled in the TypeScript SDK ("Order not found: Order not found: 123") but not in Python, producing inconsistent, confusing error text surfaced to end users/logs depending on which SDK is used against the identical server response.


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