Get this on every PR automatically. Webhook fires, AI reviews, you approve.
Install on stripe/link-cli →
// reviewed by pulllight
PullLight found 5 issues
4 high
1 medium
// convert to your repo
PullLight caught 5 4 high, 1 medium on this PR.
Install on stripe/link-cli to get this on every PR automatically — 60 seconds.
Findings
5 issues
high
race-condition
`nextRequestId` is a module-level mutable variable (`let nextRequestId = 1`). In environments where multiple `UcpResource` instances are used concurrently (e.g., multiple parallel requests), this shared counter will be mutated by all instances simultaneously, producing duplicate or out-of-order JSON-RPC request IDs. This can cause response correlation failures in the MCP transport. The counter should be an instance variable on `UcpResource` instead.
// packages/sdk/src/resources/ucp.ts:97
high
race-condition
`discoveryCache` and `negotiatedCache` are instance-level `Map`s with no concurrency guard. If two callers invoke `discover()` (or `callOperation()`) for the same URL simultaneously before the first fetch resolves, both will miss the cache and issue duplicate network requests. More critically, both will write to the cache independently — this is a classic check-then-act race. The first write wins, but the second fetch result is silently discarded, which is benign for discovery but could be a problem if the negotiated endpoint changes between the two fetches. Consider using a promise-based cache (store the in-flight promise, not the resolved value) to deduplicate concurrent calls.
// packages/sdk/src/resources/ucp.ts:104
high
error-handling
`JSON.parse(c.options.input)` and `JSON.parse(c.options.lineItems)` (lines ~113, ~120, ~138, ~155, ~175, ~195, ~210) are called without any try/catch. If the user passes malformed JSON, this will throw an unhandled exception that will likely produce an unhelpful stack trace rather than a user-friendly error message. All JSON.parse calls on user-supplied CLI input should be wrapped in try/catch and surface a clear error.
// packages/cli/src/commands/ucp/index.tsx:113
medium
other
`XDG_DATA_HOME` is hardcoded to `/tmp/link-cli-ucp-test` for all test runs. If tests run in parallel (e.g., multiple CI workers on the same machine), they will share this directory and can corrupt each other's state. Use a unique temp directory per test run (e.g., via `os.tmpdir()` + a random suffix or a `mkdtemp` call).
// packages/cli/src/__tests__/ucp.test.ts:52
high
logic
In `catalogSearch`, the spread `...rest` from `const { query, limit, cursor, ...rest } = params` is merged at the top level of `args` alongside the `catalog` key. If `params` contains any key that collides with `catalog` (which is valid per the `[key: string]: unknown` index signature on `UcpCatalogSearchParams`), the caller-supplied value will silently overwrite the constructed `catalog` object. This is an unintended data-injection path — a caller could pass `{ query: 'x', catalog: { malicious: true } }` and override the structured catalog payload sent to the merchant endpoint.
// packages/sdk/src/resources/ucp.ts:130