Skip to content

GFQL remote plumbing batch: NaN/inf leaks requests.InvalidJSONError; output= silently dropped for non-Let; shape variant has no params; node_col_subset leaves a dangling _node binding #1960

Description

@lmeyerov

Category: bare-crash (raw leak) + silent parameter drops + invalid returned Plottable. All repro-backed on master (84be35fb3) with a mocked transport. Low/medium severity batch from the 2026-08 remote-surface amplification probe; filed together because they all live in chain_remote_generic's request/response plumbing.

1. NaN / inf in a filter value leaks a raw requests.exceptions.InvalidJSONError

g.gfql_remote([n({'x': float('nan')})], api_token='tok', dataset_id='ds-1', format='json')
# requests.exceptions.InvalidJSONError: Out of range float values are not JSON compliant: nan

Same for float('inf') and for a NaN inside a predicate (n({'x': gt(float('nan'))})). The local engine accepts these values and returns a definite answer; the remote path dies with a requests exception the caller never imported. Note the contrast: every other non-JSON-able filter value (np.int64, pd.Timestamp, datetime, pd.NA, pd.NaT, bytes, np.bool_) is correctly declined with a typed GFQLTypeError from _filter_dict_to_json's validator — NaN/inf slip through that check and blow up in requests.

Expected: extend the same typed check to non-finite floats (a NaN literal is not valid JSON, so this can never go on the wire), or serialize to a defined null/predicate.

2. output= is silently ignored for non-Let queries

chain_remote.py:230-231 only attaches gfql_output inside the if is_let: branch:

g.gfql_remote([n()], output='foo')
# body -> {"gfql_operations": [...], "gfql_query": {...}, "format": "json", "engine": "pandas"}   # no gfql_output
g.gfql_remote({'type':'Let', 'bindings': {...}}, output='foo')
# body -> {..., "gfql_output": "foo", ...}

A user who names an output on a chain (or on a Cypher string that happens to compile to a flat chain — the same source text can compile either way depending on whether it needs bindings) gets no error and no effect. Expected: honor it, or raise a typed error saying output requires a Let/DAG query.

3. gfql_remote_shape() cannot take params (or output) at all

ComputeMixin.gfql_remote_shape and chain_remote_shape omit both parameters (ComputeMixin.py:824-846, chain_remote.py:438-493), while gfql_remote has them. Consequence: a parameterized Cypher query can be executed remotely but not shape-queried remotely.

g.gfql_remote_shape("MATCH (a) WHERE a.x > $cut RETURN a")
# GFQLValidationError: [missing-required-field] Missing Cypher parameter '$cut' ...

The error is typed and clear (good), but there is no way to satisfy it through this entrypoint. Expected: thread params/output through the shape variants.

4. node_col_subset can return a Plottable whose _node binding names a column that no longer exists

chain_remote.py:330 / 404 rebind the returned frames onto self's bindings without checking that the bound id/source/destination columns survived the requested column subset:

# server returns nodes without the bound 'id' column
out = g.gfql_remote([n()], node_col_subset=['x'], format='json')
out._node        # 'id'
out._nodes       # [{'x': 'a'}]   -- no 'id' column

No error at the boundary; the invalid Plottable fails later, somewhere else, with a confusing message. Expected: either drop the stale binding, or raise a typed error naming the missing bound column. (This is exactly the class of check validate_graph_shape() was introduced for in #1951, applied to results rather than inputs.)

Cross-ref #1916.

Activity

  1. added a commit that references this issue on Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions