Skip to content

v0.7.0 — Fulfillment tools, API version validation, ShopifyQL throttling

Choose a tag to compare

@callobuzz callobuzz released this 26 Aug 08:19
· 6 commits to main since this release

What's New

Fulfillment is now built in — 3 tools, 59 → 62

get_fulfillment_orders, create_fulfillment and update_fulfillment_tracking shipped only as
custom-tools/*.yaml examples. They resolved just for consumers who copied that YAML into their own
custom_paths — everyone else got "tool not found", and there was no built-in way to mark an order
fulfilled or attach tracking. They are now real tools in src/shopify/tools/orders/.

  • get_fulfillment_orders — fulfillment orders for an order, with assigned location and line items
  • create_fulfillment — fulfil an order, optionally with tracking and customer notification
  • update_fulfillment_tracking — attach or replace tracking info on an existing fulfillment

API version validation at startup

Shopify serves an unrecognised api_version with its oldest supported schema and an HTTP 200 —
never an error. A typo or an aged-out version therefore survived for months and then broke with no
config change on your side.

api_version is now checked against the known quarterly support windows at boot. Malformed or
aged-out fails startup with a message naming the cause; nearing end of support, or newer than this
build's table, boots with a warning. COB_SHOPIFY_ALLOW_UNSUPPORTED_API_VERSION=true overrides the
hard failure.

ShopifyQL rate limiting is now understood and handled

Analytics requests are billed against extensions.shopifyqlCost — a second allowance, separate from
the familiar extensions.cost leaky bucket, with no restore rate, that resets in full at a
window boundary (observed: one minute). It was not tracked at all, and the resulting throttle
arrives as a THROTTLED GraphQL error inside an HTTP 200, raised outside the retry wrapper — so
it could never be retried and surfaced as a bare "Rate limited. Please retry later."

The client now waits exactly until the window resets (default up to 10s,
rateLimit.maxShopifyQLWaitMs), and past that fails with a message naming the reset time and why an
immediate retry cannot help. It also warns when the allowance drops below 10%. Cost scales steeply
with the range scanned — one wide grouped query measured 259 points of 1000.

Observability

loadYamlToolsDetailed() reports per-path load counts and skipped paths. The startup log line now
carries builtInTools, customTools, resolved customToolPaths with per-path counts,
customToolPathsSkipped with reasons, and apiVersion.

Security

All 27 open dependency advisories resolved — pnpm audit clean at every severity, prod and dev.

Package From To
hono 4.12.22 4.13.4
@hono/node-server 1.19.13 1.19.17
fast-uri 3.1.2 3.1.6
ip-address 10.2.0 10.5.0
postcss 8.5.15 8.5.26
vite 8.0.8 8.2.2
body-parser 2.2.2 2.3.0
esbuild 0.27.4 0.28.2
nanoid 3.3.12 3.3.18

Why these kept coming back. The pnpm.overrides floors were not actually pinning anything. Each
had been written as >=<the version current when it was added>, and the already-locked version kept
satisfying that range — so pnpm install never re-resolved. Every package sat at exactly its
floor (hono@4.12.22 under >=4.12.22), and the override looked like a patched pin while silently
absorbing each new advisory against the same package. That is why hono, fast-uri, ip-address
and postcss were re-flagged after both 0.6.5 and 0.6.6.

The floors are now caret ranges. Carets invalidate the stale lock entry and cap the major — a
plain >= would now pull fast-uri 4.x and @hono/node-server 2.x in underneath the MCP SDK.

Bug Fixes

  • cob-shopify --version reported 0.6.0 at package version 0.6.6. VERSION was a
    hand-maintained constant, and the test guarding it asserted that same literal — restating the
    constant instead of checking it, so the two drifted apart across six releases. It now compares
    against package.json.
  • pnpm lint failed on Windows for files you never touched. Sources are stored LF, but
    core.autocrlf=true (the Git for Windows default) rewrote them to CRLF on checkout, and Biome's
    formatter requires LF. Added .gitattributes (* text=auto eol=lf). Lint also no longer
    format-checks git-ignored paths.
  • Concurrent JSON storage writes dropped tokens. Writes were read-modify-write with no
    serialization, so overlapping token refreshes — ordinary for parallel tool calls — lost all but
    the last. The "atomic write" also used a fixed <file>.tmp, so two writers raced the same path
    and failed with ENOENT (or EPERM on Windows). Writes are now serialized in-process with unique
    temp names, and transient Windows rename errors are retried.
  • refund_rate_summary works on both sides of Shopify's "sales reversals" rename.
  • pnpm test:integration now runs serially. It fanned out across ~20 workers, each with its own
    rate limiter and no shared view of the per-store ShopifyQL allowance, so the suite throttled
    itself and reported budget exhaustion as tool failures.
  • ToolDefinition.cliAction overrides the derived CLI action name. Mechanical domain-word
    stripping turned get_fulfillment_orders into orders get-fulfillment, losing the subject and
    colliding visually with get-fulfillment-status. It is now orders get-fulfillment-orders.

Numbers

  • 62 built-in tools across 5 domains — Products (15), Orders (15), Customers (9),
    Inventory (7), Analytics (16)
  • 663 tests passing
  • 0 open dependency advisories

Full Changelog: v0.6.6...v0.7.0