Skip to content

connector-openapi: query parameters are sent through String(value), so an object or array value (style: deepObject, explode) goes out as [object Object] or a comma list instead of the encoding the document declares #28504

Closed

Description

@objectstack-fleet

epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

Filing gate: ① a product defect with a named location, finding class (a) (reproducible). Found by the os-dev on #28276 (the Stripe app, PR #28503) and re-read by this seat. Child of #27293 (E1). ⛔ Not a claim.

reach: public door — POST /api/v1/automation/:flow/trigger running a connector_action on a declarative openapi instance. Named real producer: Stripe's spec3.json (stripe/openapi, MIT), whose list operations declare expand, created and due_date as query parameters with style: deepObject (GetInvoices, GetCharges, GetCustomers, GetPaymentIntents). Measured by the dev (one-off bootStack probe, fake fetchImpl recording the URL): created: { gte: 1700000000 } went out as created=%5Bobject+Object%5D, expand: ['a', 'b'] as expand=a%2Cb, and the run answered 200. Stripe's own answer is NOT MEASURED (no key).

The defect

packages/connectors/connector-openapi/src/openapi-connector.ts:563 (read at origin/main 2026-11-01T03:40Z): url.searchParams.set(key, String(value)); the path builder (:592) does the same with encodeURIComponent(String(value)). The parameter's style / explode (OpenAPI 3.x Parameter Object) are never read, while the action's input schema advertises the object/array shape — so an author or an AI that fills the advertised input sends a malformed request, and nothing says so.

Seam: spec: OpenAPI 3.x Parameter Object style / explode → runtime: packages/connectors/connector-openapi/src/openapi-connector.ts buildUrl / stringifyValues

Work

Serialize query (and path) parameters by their declared style and explode (at least form explode true/false for arrays, deepObject for objects; the OpenAPI 3.x defaults when absent), or refuse at materialization an operation whose parameter uses a style the connector does not serialize — never String() a non-scalar. Tests: the three Stripe shapes above as fixtures, plus a scalar control.

Business reason

Every list call on Stripe (and most large vendor APIs) filters by date ranges or expands related objects through these parameters; today the request is silently wrong.

Reader

The triage seat grades and routes it (domain:server, connector-openapi); the epic PM of #27293 dispatches.

Dedupe

The 500 most recent issues and PRs (read 2026-11-01T03:42Z), titles grepped for deepObject|explode|query string|object Object: 0 hits; control openapi on the same set: 7 hits.

Activity

  1. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 审批与自动化 | none | P②

    Triage: p2 · bug · domain:server · area:automation · pm:epic kept · 2026-11-01T03:55Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Claim: PM loop round 4
    Session: session_017xRBcVBKAFk1ntXSjvPt7U
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-28504-openapi-param-style
    Worktree: objectstack-issue-28504
    Domain: domain:server
    Seat: epic:#27293#1
    File surface: packages/connectors/connector-openapi/src/** (the query and path builders and the operation materializer that reads each parameter's style / explode) and its tests and fixtures; content/docs/automation/connectors.mdx if it states how parameters are sent; a @objectstack/connector-openapi changeset (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT — dispatch-gates --tier --repo objectstack-ai/objectstack this fire: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: connector-openapi's URL builder (openapi-connector.ts:563, path at :592) passes every parameter value through String() and never reads the Parameter Object's style / explode | no other path serializes connector_action query input; the action's input schema advertises the object/array shape | any author or AI that fills a deepObject or array parameter on a declarative openapi instance; Stripe's list operations (PR #28503) are the first on the shelf
    Thread-read: 6553109544
    Premise: holds (read at origin/main 23d1303d 2026-11-01T04:06Z: url.searchParams.set(key, String(value)) at :563, encodeURIComponent(String(value)) at :592; style / explode 0 reads in that file)
    Serial constraints cleared: none — none of the 60 most recent open PRs touches packages/connectors/connector-openapi/src/** (files read 2026-11-01T04:06Z).

  3. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
    os-dev-report

    {
      "issue": 28504,
      "status": "done",
      "branch": "claude/issue-28504-openapi-param-style",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/28537",
      "session": "session_017xRBcVBKAFk1ntXSjvPt7U",
      "premise_still_valid": true,
      "summary": "Premise held at 23d1303d: query values went through stringifyValues/String() (openapi-connector.ts:326/601, then :563) and path values through encodeURIComponent(String()) (:592); headers took the same stringifyValues path; cookie parameters are never sent (left out of the input schema). style/explode survive into the operation record: the normalizer copies parameter objects whole, and the packaged Stripe document carries style deepObject + explode true on created/due_date/expand. New src/parameter-serialization.ts encodes every path/query/header value by its declared style/explode or the OAS 3.x location default (query form+explode; path/header simple), implementing form, spaceDelimited, pipeDelimited, deepObject, simple, label, matrix; deepObject on an array brackets each index (expand[0]=a), the style's own rule applied to the array's keys. A style OAS 3.x does not define for the location (or a non-boolean explode) refuses the operation at materialization naming operation+parameter (an include that omits it clears it); a value shape the style cannot carry, or a non-scalar for a content-typed parameter, fails the dispatch before fetch. Scalars are byte-identical to before (ablation: both scalar controls green on the old code). Docs: connectors.mdx states how parameters are sent plus two failure-table rows; the connector-stripe README Limits bullet that told authors to leave expand/created out was made false by this change and is corrected (outside the claim's file surface, see deviations). Changeset: @objectstack/connector-openapi patch.",
      "tests": "HEAD 73e2a2ec. src/parameter-style.test.ts 17/17 (three Stripe shapes with GetInvoices' five parameter objects copied verbatim; one-off canonical-JSON compare against packages/apps/connector-stripe/openapi/stripe.openapi.json: 'packaged 5 fixture 5 equal true'; query and path scalar controls; style table; api-key query merge; both refusals, dispatch refusal asserts ParameterValueError + 0 fetch calls). pnpm --filter @objectstack/connector-openapi test: Test Files 9 passed (9), Tests 109 passed (109). typecheck exit 0, check:test-typecheck OK (1 file / 5 errors / 1 signature, pre-existing ledger unchanged). pnpm --filter @objectstack/connector-app-stripe test: objectstack validate passed, Test Files 2 passed (2), Tests 16 passed (16). Ablation (fix committed first; base openapi-connector.ts written over the file, mutant 'String(value)' count 2 / 'serializeQuery' count 0, trap restore to HEAD, git hash-object re-matched HEAD blob, git diff HEAD empty): 15 failed | 2 passed (17), the 2 green being the scalar controls; old outputs created=%5Bobject+Object%5D, expand=data.customer%2Cdata.subscription, due_date=%5Bobject+Object%5D. No dist rebuild leg needed: the test imports src by relative path. Public-door one-off probe (not committed, deleted): packaged Stripe app via bootStack, POST /automation/:flow/trigger on a GetInvoices flow answered 200, fake Stripe received https://api.stripe.com/v1/invoices?created%5Bgte%5D=1700000000&due_date%5Bgte%5D=1700000000&due_date%5Blt%5D=1702592000&expand%5B0%5D=data.customer&expand%5B1%5D=data.subscription&limit=3. Stripe's own answer NOT MEASURED (no key).",
      "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 88 commands for the 6 changed paths at 73e2a2ec (after merging origin/main eeec88d1); all 88 run, exit codes captured before any pipe: 88 exit 0. First pass at 62f84496 had 2 exit 3 (check:skill-examples needed client-react dist, check:dual-build-cjs-loads needed built dists); both re-ran exit 0 after building the closures. --ran reconciliation: '88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. check-nul-bytes exit 0; control-byte self-scan 0 hits. No export renamed or removed (stringifyValues was module-private), so check-platform-checklist --changed-from was not owed. CI check-runs on 73e2a2ec at report time: Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI, release & engine gates; ESLint; PM dispatch-gates; docs & code guards; repo & tooling gates; scoped query-options erasure; scoped slot lookup & corpus; scoped tooling self-tests), PR-body guards, Spec property liveness, Type Check (consumer gates; debt ledger; source gates; workspace), filter = in_progress; 18.x upgrade harness, Packed-tarball smoke, Schedule gate = skipped. Test Core / Build Core not yet listed. test:repo not run locally (diff touches no repo-project test), declared to CI.",
      "dogfood_battery": "n/a — no `!` in the PR title and Clause-② is `no` with no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back 6284 bytes identical) and this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/28504/comments); plus git push of the branch (not a REST write)",
      "open_questions": [
        {
          "question": "deepObject on an array is undefined in OAS 3.x; Stripe declares it on expand. Which wire form?",
          "options": [
            "A expand[0]=a&expand[1]=b (each index in brackets: deepObject's property-in-brackets rule applied to the array's keys; also unambiguous for nested arrays of objects)",
            "B expand[]=a&expand[]=b (the bracket-list convention Stripe's curl docs show)"
          ],
          "recommendation": "A, shipped in this PR, because it is derived from the declared style rather than a vendor convention; non-blocking — Stripe's acceptance of either is NOT MEASURED here"
        }
      ],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — cookie parameters are left out of the action's input schema and never sent (pre-existing, unchanged; no public-door reach measured)",
        "carrier: none · noted, not filed — Parameter Object allowReserved is not read; reserved characters stay percent-encoded as before"
      ],
      "deviations": "One file outside the claim's file surface: packages/apps/connector-stripe/README.md (private package) — its Limits bullet said expand/created[gte] are not sent in Stripe's form so leave them out; this change made that false, so the bullet now says how they are sent and that Stripe's answer is not measured. Merged origin/main once (eeec88d1) before opening the PR, which brought PR #28503's Stripe app onto the branch base. Commits carry the model-free trailer pair, not the harness reminder's model-named Co-Authored-By.",
      "files_changed": [
        ".changeset/connector-openapi-parameter-style.md",
        "content/docs/automation/connectors.mdx",
        "packages/apps/connector-stripe/README.md",
        "packages/connectors/connector-openapi/src/openapi-connector.ts",
        "packages/connectors/connector-openapi/src/parameter-serialization.ts",
        "packages/connectors/connector-openapi/src/parameter-style.test.ts"
      ]
    }
  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    MERGED → PR #28537 through the merge queue (merge commit a52191f69) · 2026-11-01T06:00Z. Closed completed by its Fixes #28504. pm:dispatched is removed in this act; pm:epic is kept. Every path, query and header parameter of a declarative openapi instance is now sent by its declared style / explode, so the Stripe app's created, due_date and expand filters leave in the form its document declares. Whether Stripe itself accepts them is NOT MEASURED (no key).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions