Skip to content

feat: delete & list clients using wgc#2795

Open
comatory wants to merge 4 commits intomainfrom
ondrej/eng-7122-delete-client
Open

feat: delete & list clients using wgc#2795
comatory wants to merge 4 commits intomainfrom
ondrej/eng-7122-delete-client

Conversation

@comatory
Copy link
Copy Markdown
Contributor

@comatory comatory commented Apr 27, 2026

This PR introduces new command clients with list and delete subcommands. Even though the
original ticket called for adding just the "delete" action, I felt it did not make sense
to have it without an action for listing clients.

Clients are tied to persisted operations. Deleting them cascades the operations which
were used for the client. This means the delete operation has confirmation prompt to avoid
deleting persisted operations by mistake.

I haven't implemented any traffic check, so this means the prompt is triggered every time
there's at least a single operation.

Summary by CodeRabbit

  • New Features

    • Added a new "clients" CLI with list (defaults to namespace default) and delete (includes a preview showing impacted persisted operations and detailed deleted-operations on success). Both support --json and --force.
  • Documentation

    • Added CLI docs and examples for clients overview, list, and delete; navigation updated to include a Clients group.
  • Tests

    • Added tests covering list/delete flows, JSON output, confirmation/force behavior, and error cases.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/docs-website.
  • I have read the Contributors Guide.

Open Source AI Manifesto

This project follows the principles of the Open Source AI Manifesto. Please ensure your contribution aligns with its principles.

@mintlify
Copy link
Copy Markdown
Contributor

mintlify Bot commented Apr 27, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
wundergraphinc 🟢 Ready View Preview Apr 27, 2026, 12:15 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a new clients CLI group with list and delete commands; introduces PreviewDeleteClient/DeleteClient protobuf RPCs and connect descriptors; implements backend handlers and repository methods to preview and delete registered GraphQL clients (including persisted-operation snapshots and blob removal); adds tests and CLI docs/navigation.

Changes

Cohort / File(s) Summary
CLI commands
cli/src/commands/clients/index.ts, cli/src/commands/clients/commands/list.ts, cli/src/commands/clients/commands/delete.ts, cli/src/commands/index.ts
Adds clients command group with list (table/JSON, namespace default 'default') and delete (preview RPC, --force, --json, interactive confirm) subcommands; registers auth preAction.
CLI tests
cli/test/clients-list.test.ts, cli/test/clients-delete.test.ts
New Vitest suites validating list/delete flows: RPC mocking, stdout/JSON outputs, prompting and --force, namespace defaulting, error handling, exit-code and stderr behavior.
Proto / connect artifacts
proto/wg/cosmo/platform/v1/platform.proto, connect/src/wg/cosmo/platform/v1/platform_pb.ts, connect/src/wg/cosmo/platform/v1/platform_connect.ts, connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
Adds PreviewDeleteClient and DeleteClient RPCs and associated request/response messages; regenerates connect method descriptors and message classes.
Platform service wiring
controlplane/src/core/bufservices/PlatformService.ts
Registers new previewDeleteClient and deleteClient RPC handlers on the PlatformService implementation.
Backend handlers
controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts, controlplane/src/core/bufservices/persisted-operation/deleteClient.ts
New handlers that default namespace, authenticate/enforce RBAC/org state, preview affected persisted operations, remove client blobs when required, perform deletion, attempt manifest regeneration, and return structured responses with error handling.
Repository layer
controlplane/src/core/repositories/OperationsRepository.ts
Adds getRegisteredClientByName, previewDeleteClient, deleteClient and a DeletedClientOperationDTO; implements transactional snapshotting of deleted persisted-operations and safe deletion logic.
Backend tests
controlplane/test/persisted-operations.test.ts
Adds tests covering preview/delete success paths, not-found and RBAC denial, blob-removal failure handling, manifest regeneration behavior, and post-deletion state checks.
Docs & navigation
docs-website/cli/clients.mdx, docs-website/cli/clients/list.mdx, docs-website/cli/clients/delete.mdx, docs-website/docs.json
New CLI documentation pages for clients list/delete and updates to docs navigation to include a Clients group with list and delete routes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: delete & list clients using wgc' clearly and concisely describes the main change: adding delete and list functionality for GraphQL clients via the wgc CLI command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 27, 2026

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-2d0bdfda9f1a9ddcdc707c4ff2635b70ffdb2f6e

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

❌ Patch coverage is 83.76963% with 93 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.15%. Comparing base (a6a6956) to head (d811b22).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...re/bufservices/persisted-operation/deleteClient.ts 67.00% 33 Missing ⚠️
cli/src/commands/clients/commands/delete.ts 83.51% 30 Missing ⚠️
cli/src/commands/clients/commands/list.ts 85.29% 15 Missing ⚠️
...ervices/persisted-operation/previewDeleteClient.ts 82.22% 8 Missing ⚠️
...lane/src/core/repositories/OperationsRepository.ts 94.82% 6 Missing ⚠️
cli/src/commands/clients/index.ts 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2795      +/-   ##
==========================================
- Coverage   41.70%   41.15%   -0.56%     
==========================================
  Files         791      995     +204     
  Lines      113325   124710   +11385     
  Branches     8768     5752    -3016     
==========================================
+ Hits        47266    51319    +4053     
- Misses      65695    71692    +5997     
- Partials      364     1699    +1335     
Files with missing lines Coverage Δ
cli/src/commands/index.ts 85.15% <100.00%> (+0.73%) ⬆️
...ntrolplane/src/core/bufservices/PlatformService.ts 82.81% <100.00%> (+0.18%) ⬆️
cli/src/commands/clients/index.ts 92.85% <92.85%> (ø)
...lane/src/core/repositories/OperationsRepository.ts 92.19% <94.82%> (+0.58%) ⬆️
...ervices/persisted-operation/previewDeleteClient.ts 82.22% <82.22%> (ø)
cli/src/commands/clients/commands/list.ts 85.29% <85.29%> (ø)
cli/src/commands/clients/commands/delete.ts 83.51% <83.51%> (ø)
...re/bufservices/persisted-operation/deleteClient.ts 67.00% <67.00%> (ø)

... and 309 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@comatory comatory changed the title feat: CLI clients command to list & delete clients feat: delete & list clients using wgc Apr 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (5)
controlplane/test/persisted-operations.test.ts (2)

636-783: Add direct error-path coverage for deleteClient.

The new suite exercises auth/not-found on previewDeleteClient, but the destructive RPC itself is only covered for success and blob-storage failure. Since deleteClient has its own handler, please add at least unauthorized and missing-client assertions here so regressions are not masked by the preview step.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 636 - 783, Add
tests to cover error paths for the actual deleteClient RPC (not only
previewDeleteClient): call client.deleteClient with an unauthorized context
(e.g., missing/invalid credentials or a user without permission) and assert the
response code is ERR and message indicates unauthorized, and call
client.deleteClient for a non-existent client name and assert it returns
NOT_FOUND (or appropriate error) and that no side-effects occur (clients list
unchanged, blobStorage keys unchanged). Place these assertions alongside the
existing deleteClient tests and reuse SetupTest, genID, and getClients to locate
and verify state; reference client.deleteClient, client.getClients, and
blobStorage.removeDirectory to identify the code under test.

676-693: Avoid depending on deletedOperations order.

This assertion hardcodes the returned array order. Unless the handler guarantees a stable sort, this will make the test flaky.

♻️ Suggested change
-      expect(
-        deleteResp.deletedOperations.map((op) => ({
-          id: op.id,
-          operationId: op.operationId,
-          operationNames: op.operationNames,
-        })),
-      ).toStrictEqual([
+      expect(
+        deleteResp.deletedOperations
+          .map((op) => ({
+            id: op.id,
+            operationId: op.operationId,
+            operationNames: op.operationNames,
+          }))
+          .sort((left, right) => left.operationId.localeCompare(right.operationId)),
+      ).toStrictEqual([
         {
           id: expect.any(String),
           operationId: helloOperationId,
           operationNames: [],
         },
         {
           id: expect.any(String),
           operationId: typenameOperationId,
           operationNames: [],
         },
-      ]);
+      ].sort((left, right) => left.operationId.localeCompare(right.operationId)));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 676 - 693, The
test currently assumes a stable order of deleteResp.deletedOperations which
makes it flaky; update the assertion to be order-insensitive by either
transforming and sorting deleteResp.deletedOperations by a stable key (e.g.,
operationId) before comparing, or use an order-agnostic matcher such as
expect.arrayContaining with objects that reference helloOperationId and
typenameOperationId; operate on deleteResp.deletedOperations (and map to {id,
operationId, operationNames}) and compare against the expected items without
relying on array order.
cli/test/clients-list.test.ts (1)

58-68: Restore the global spies after each test.

console.log, process.stderr.write, and process.exit stay mocked across cases here. Without vi.restoreAllMocks() (or per-spy mockRestore()), this suite depends on global Vitest reset config and can become order-dependent.

Suggested fix
   afterEach(() => {
     process.exitCode = undefined;
+    vi.restoreAllMocks();
   });

Also applies to: 140-150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-list.test.ts` around lines 58 - 68, The test suite leaves
console.log, process.stderr.write and process.exit mocked (via logSpy,
stderrSpy, exitSpy) which can leak between tests; restore those spies in
afterEach by calling vi.restoreAllMocks() or mockRestore() on each spy
(logSpy.mockRestore(), stderrSpy.mockRestore(), exitSpy.mockRestore()) so the
global environment is returned to normal; update the afterEach block that
currently only resets process.exitCode to also restore the mocks used in
beforeEach for the tests around clients-list.test.ts (and similarly at lines
~140-150).
cli/test/clients-delete.test.ts (2)

24-25: Replace any in request callbacks with concrete request types (or unknown).

These callback signatures currently bypass type-safety and make test helpers permissive in ways that can hide request-shape regressions.

Proposed typed callback shape
+interface ClientDeleteRequestShape {
+  namespace?: string;
+}
+
 function createMockTransport(
   previewResponse: PartialMessage<PreviewDeleteClientResponse>,
   deleteResponse: PartialMessage<DeleteClientResponse>,
-  onPreviewDeleteClient?: (req: any) => void,
-  onDeleteClient?: (req: any) => void,
+  onPreviewDeleteClient?: (req: ClientDeleteRequestShape) => void,
+  onDeleteClient?: (req: ClientDeleteRequestShape) => void,
 ) {

As per coding guidelines: "Avoid any type in TypeScript; use specific types or generics."

Also applies to: 45-46

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-delete.test.ts` around lines 24 - 25, The test helper
callback signatures onPreviewDeleteClient and onDeleteClient currently use the
unsafe type `any`; update them to use concrete request types (or `unknown`) to
restore type-safety—replace the `any` parameter types in the function signatures
for onPreviewDeleteClient and onDeleteClient with the appropriate request
interface used by the delete flow (or `unknown` if a shared interface isn't
available), and apply the same change to the other occurrences noted (the
callbacks at the second pair on lines 45-46) so the tests will catch
request-shape regressions.

21-39: Add explicit return types to helper functions.

createMockTransport and getJsonOutput currently rely on inferred return types. Making these explicit improves maintainability and aligns with the repository TS rules.

Suggested return type annotations
 function createMockTransport(
   previewResponse: PartialMessage<PreviewDeleteClientResponse>,
   deleteResponse: PartialMessage<DeleteClientResponse>,
   onPreviewDeleteClient?: (req: ClientDeleteRequestShape) => void,
   onDeleteClient?: (req: ClientDeleteRequestShape) => void,
-) {
+): ReturnType<typeof createRouterTransport> {
   return createRouterTransport(({ service }) => {
     service(PlatformService, {
       previewDeleteClient: (req) => {
         onPreviewDeleteClient?.(req);
         return previewResponse;
@@
-function getJsonOutput(logSpy: MockInstance<typeof console.log>) {
+function getJsonOutput(logSpy: MockInstance<typeof console.log>): unknown {

As per coding guidelines: "Use explicit type annotations for function parameters and return types in TypeScript."

Also applies to: 68-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-delete.test.ts` around lines 21 - 39, Add explicit
TypeScript return annotations for the helper functions: annotate
createMockTransport to return ReturnType<typeof createRouterTransport> (so the
transport type is explicit) and annotate getJsonOutput to return
Promise<unknown> (or a more specific Promise<T> if you know the JSON shape);
update the function signatures (createMockTransport and getJsonOutput)
accordingly and ensure any parameter types remain explicit to satisfy the
repository's TS rules.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/commands/clients/commands/delete.ts`:
- Around line 39-127: The previewDeleteClient and deleteClient RPC calls invoked
inside command.action can throw before response.code exists; wrap each call (the
calls to opts.client.platform.previewDeleteClient and
opts.client.platform.deleteClient) in try-catch blocks, catch any thrown error,
and convert it to the same CLI error path: if options.json produce
createJsonErrorOutput (use EnumStatusCode.ERR and include
error.message/details), console.log(JSON.stringify(...)), set process.exitCode =
1 and return; otherwise log the error detail via console.log(pc.red(...)) and
call program.error(pc.red('Could not delete client.')) so failures are
normalized to the same behavior as non-exception responses (references:
command.action, previewResp, resp, createJsonErrorOutput, program.error,
options.json).

In `@cli/src/commands/clients/commands/list.ts`:
- Around line 29-80: Wrap the call to opts.client.platform.getClients inside a
try-catch within the command.action handler (around where getClients is called
in the anonymous async callback) so any thrown RPC/transport errors are caught;
in the catch block, produce the same JSON error shape when options.json is true
(using createJsonErrorOutput and logging JSON) and otherwise call
program.error(pc.red('Could not fetch clients.')) or
console.log(pc.red(error.message)) and set process.exitCode = 1 to preserve
existing exit behavior. Ensure the rest of the logic that inspects resp.response
only runs after a successful await of getClients.

In `@controlplane/src/core/bufservices/persisted-operation/deleteClient.ts`:
- Around line 60-76: The code calls opts.blobStorage.removeDirectory({ key:
clientDirectory }) unconditionally which can fail if there are no persisted
operations; update the logic in the delete client flow (where clientDirectory
and removeDirectory are used) to first check preview.persistedOperationsCount
(or the equivalent preview object) and skip calling
opts.blobStorage.removeDirectory when persistedOperationsCount === 0, ensuring
the DB delete still proceeds and the same success/error response shape is
returned when deletion is skipped; keep the existing try/catch around
removeDirectory for the non-zero case so errors from actual removals are still
handled and logged.

In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 371-425: The current transaction in OperationsRepository selects
the client and then queries federatedGraphPersistedOperations before deleting
the client, which allows a race where new child operations are inserted and then
cascaded away but not captured; to fix, within the same transaction either
acquire a row lock on the client (SELECT ... FOR UPDATE via
tx.query.federatedGraphClients.findFirst with a locking option or equivalent)
right after finding the client (use federatedGraphClients,
this.federatedGraphId, clientName, client.id to locate it) before enumerating
children, or instead delete the child rows explicitly using
tx.delete(federatedGraphPersistedOperations).where(...matching federatedGraphId
and client.id).returning(...) to capture deletedOperations and
deletedOperationsCount, then delete the federatedGraphClients row; ensure all
operations remain inside the existing transaction so the snapshot accurately
reflects deleted children.

In `@proto/wg/cosmo/platform/v1/platform.proto`:
- Around line 2151-2168: Add explicit confirmation/precondition fields to the
DeleteClientRequest so the server can verify caller intent: add a bool force
flag and an int32 expected_deleted_operations_count (or
expectedDeletedOperationsCount) and/or repeated string expected_operation_ids to
DeleteClientRequest, then change the DeleteClient RPC handler to validate these
fields (compare expected count/ids to what will be deleted and reject if
mismatched unless force==true). Update any callers/CLI and the
PreviewDeleteClient flow to populate these new fields when performing the final
DeleteClient call so the server can enforce the safety check.

---

Nitpick comments:
In `@cli/test/clients-delete.test.ts`:
- Around line 24-25: The test helper callback signatures onPreviewDeleteClient
and onDeleteClient currently use the unsafe type `any`; update them to use
concrete request types (or `unknown`) to restore type-safety—replace the `any`
parameter types in the function signatures for onPreviewDeleteClient and
onDeleteClient with the appropriate request interface used by the delete flow
(or `unknown` if a shared interface isn't available), and apply the same change
to the other occurrences noted (the callbacks at the second pair on lines 45-46)
so the tests will catch request-shape regressions.
- Around line 21-39: Add explicit TypeScript return annotations for the helper
functions: annotate createMockTransport to return ReturnType<typeof
createRouterTransport> (so the transport type is explicit) and annotate
getJsonOutput to return Promise<unknown> (or a more specific Promise<T> if you
know the JSON shape); update the function signatures (createMockTransport and
getJsonOutput) accordingly and ensure any parameter types remain explicit to
satisfy the repository's TS rules.

In `@cli/test/clients-list.test.ts`:
- Around line 58-68: The test suite leaves console.log, process.stderr.write and
process.exit mocked (via logSpy, stderrSpy, exitSpy) which can leak between
tests; restore those spies in afterEach by calling vi.restoreAllMocks() or
mockRestore() on each spy (logSpy.mockRestore(), stderrSpy.mockRestore(),
exitSpy.mockRestore()) so the global environment is returned to normal; update
the afterEach block that currently only resets process.exitCode to also restore
the mocks used in beforeEach for the tests around clients-list.test.ts (and
similarly at lines ~140-150).

In `@controlplane/test/persisted-operations.test.ts`:
- Around line 636-783: Add tests to cover error paths for the actual
deleteClient RPC (not only previewDeleteClient): call client.deleteClient with
an unauthorized context (e.g., missing/invalid credentials or a user without
permission) and assert the response code is ERR and message indicates
unauthorized, and call client.deleteClient for a non-existent client name and
assert it returns NOT_FOUND (or appropriate error) and that no side-effects
occur (clients list unchanged, blobStorage keys unchanged). Place these
assertions alongside the existing deleteClient tests and reuse SetupTest, genID,
and getClients to locate and verify state; reference client.deleteClient,
client.getClients, and blobStorage.removeDirectory to identify the code under
test.
- Around line 676-693: The test currently assumes a stable order of
deleteResp.deletedOperations which makes it flaky; update the assertion to be
order-insensitive by either transforming and sorting
deleteResp.deletedOperations by a stable key (e.g., operationId) before
comparing, or use an order-agnostic matcher such as expect.arrayContaining with
objects that reference helloOperationId and typenameOperationId; operate on
deleteResp.deletedOperations (and map to {id, operationId, operationNames}) and
compare against the expected items without relying on array order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 87c4cd8c-c02a-4b2a-9275-f2f898eddfbf

📥 Commits

Reviewing files that changed from the base of the PR and between ea28ed8 and 3730a28.

⛔ Files ignored due to path filters (2)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • connect-go/gen/proto/wg/cosmo/platform/v1/platformv1connect/platform.connect.go is excluded by !**/gen/**
📒 Files selected for processing (19)
  • cli/src/commands/clients/commands/delete.ts
  • cli/src/commands/clients/commands/list.ts
  • cli/src/commands/clients/index.ts
  • cli/src/commands/index.ts
  • cli/test/clients-delete.test.ts
  • cli/test/clients-list.test.ts
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/persisted-operation/deleteClient.ts
  • controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • docs-website/cli/clients.mdx
  • docs-website/cli/clients/delete.mdx
  • docs-website/cli/clients/list.mdx
  • docs-website/docs.json
  • proto/wg/cosmo/platform/v1/platform.proto

Comment thread cli/src/commands/clients/commands/delete.ts
Comment thread cli/src/commands/clients/commands/list.ts
Comment thread controlplane/src/core/repositories/OperationsRepository.ts
Comment thread proto/wg/cosmo/platform/v1/platform.proto
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
cli/src/commands/clients/commands/delete.ts (1)

39-49: ⚠️ Potential issue | 🟠 Major

Normalize thrown RPC failures.

Both platform calls can reject before a response.code exists. When that happens, this command bypasses its JSON error envelope and program.error() path, so callers get a raw exception instead of the documented CLI result shape.

As per coding guidelines "Add proper error handling with try-catch blocks".

Also applies to: 91-100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/commands/clients/commands/delete.ts` around lines 39 - 49, The
previewDeleteClient and deleteClient RPC calls (invocations of
opts.client.platform.previewDeleteClient and opts.client.platform.deleteClient)
can throw before a response.code exists; wrap each call in try-catch and
normalize any thrown error into the CLI's JSON error envelope and invoke
program.error(...) with a structured object (include a code, message, and any
available details) so callers never get a raw exception; ensure you still handle
the normal response.code branch but on catch extract whatever information is
available from the thrown error (message, status/code if present) and forward
that through program.error to keep the documented CLI result shape.
controlplane/src/core/bufservices/persisted-operation/deleteClient.ts (1)

60-76: ⚠️ Potential issue | 🟠 Major

Skip blob cleanup when there is nothing to delete.

preview.persistedOperationsCount is already available, but line 63 still calls removeDirectory() for zero-operation clients. Storage backends that treat a missing prefix as an error will reject a valid client delete here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/bufservices/persisted-operation/deleteClient.ts` around
lines 60 - 76, The code unconditionally calls opts.blobStorage.removeDirectory
for clientDirectory even when there are zero persisted operations; change
deleteClient to check preview.persistedOperationsCount (or
preview?.persistedOperationsCount) before calling
opts.blobStorage.removeDirectory and skip the blob cleanup when the count is 0
(return the normal success response or proceed without calling removeDirectory).
Locate the block around clientDirectory and the try/catch that invokes
opts.blobStorage.removeDirectory and gate that call with an if
(preview.persistedOperationsCount > 0) so storage backends that error on missing
prefixes are not invoked for empty clients.
controlplane/src/core/repositories/OperationsRepository.ts (1)

371-425: ⚠️ Potential issue | 🟠 Major

Capture deleted operations atomically.

deletedOperations are read before the parent client row is deleted. A concurrent publish in that window can be cascaded away by the final delete and still be missing from deletedOperationsCount and deletedOperations. Lock the client row first, or delete child rows with RETURNING inside the same transaction before removing the client.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` around lines 371
- 425, The code reads child rows into deletedOperations before deleting the
client, allowing a race where a concurrent insert could be cascaded away by the
client delete and not be reflected in deletedOperations; fix by performing the
child-row removal and capture atomically inside the same transaction (either
lock the client row first or delete children with RETURNING). Concretely: within
the existing transaction block that references federatedGraphClients and
federatedGraphPersistedOperations, either re-query the client using a FOR
UPDATE/row-lock (so no concurrent publishes can insert children) or replace the
separate select with a single delete on federatedGraphPersistedOperations using
RETURNING to collect deleted rows (id, operationId, operationNames) and count,
then delete the federatedGraphClients row; return the captured returned rows and
count (use the same variables client and deletedOperations names to minimize
changes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controlplane/src/core/bufservices/persisted-operation/deleteClient.ts`:
- Around line 91-103: When regenerate manifest fails in the deleteClient flow,
do not return success; instead make deleteClient surface an error so callers see
the inconsistency. Change the catch for
operationsRepo.generateAndUploadManifest({...}) to propagate/throw a non-OK
error (or return an RPC error response) instead of only logging: include context
(req.clientName, federatedGraph.id, authContext.organizationId) in the thrown
error or returned status so the RPC fails when generateAndUploadManifest()
fails. Ensure the change affects the same function handling the delete (the code
that calls operationsRepo.generateAndUploadManifest and currently logs via
logger.error) so the operation is only considered successful if manifest upload
completes.
- Around line 60-78: The blob removal currently runs before the DB delete
(removeDirectory() executed prior to operationsRepo.deleteClient()), which can
leave the DB row deleted without blobs if the DB delete later fails; change the
flow so the DB delete (operationsRepo.deleteClient) is performed and confirmed
committed before removing blobs: call
operationsRepo.deleteClient(req.clientName) inside its transactional path, only
after a successful commit attempt removeDirectory({ key: clientDirectory }), and
if removeDirectory fails implement a compensating/retry mechanism (e.g., enqueue
a background retry job or mark a cleanup-needed record) instead of returning an
error to avoid leaving inconsistent state; ensure error handling/logging
references removeDirectory and operationsRepo.deleteClient so it's clear which
step failed.

---

Duplicate comments:
In `@cli/src/commands/clients/commands/delete.ts`:
- Around line 39-49: The previewDeleteClient and deleteClient RPC calls
(invocations of opts.client.platform.previewDeleteClient and
opts.client.platform.deleteClient) can throw before a response.code exists; wrap
each call in try-catch and normalize any thrown error into the CLI's JSON error
envelope and invoke program.error(...) with a structured object (include a code,
message, and any available details) so callers never get a raw exception; ensure
you still handle the normal response.code branch but on catch extract whatever
information is available from the thrown error (message, status/code if present)
and forward that through program.error to keep the documented CLI result shape.

In `@controlplane/src/core/bufservices/persisted-operation/deleteClient.ts`:
- Around line 60-76: The code unconditionally calls
opts.blobStorage.removeDirectory for clientDirectory even when there are zero
persisted operations; change deleteClient to check
preview.persistedOperationsCount (or preview?.persistedOperationsCount) before
calling opts.blobStorage.removeDirectory and skip the blob cleanup when the
count is 0 (return the normal success response or proceed without calling
removeDirectory). Locate the block around clientDirectory and the try/catch that
invokes opts.blobStorage.removeDirectory and gate that call with an if
(preview.persistedOperationsCount > 0) so storage backends that error on missing
prefixes are not invoked for empty clients.

In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 371-425: The code reads child rows into deletedOperations before
deleting the client, allowing a race where a concurrent insert could be cascaded
away by the client delete and not be reflected in deletedOperations; fix by
performing the child-row removal and capture atomically inside the same
transaction (either lock the client row first or delete children with
RETURNING). Concretely: within the existing transaction block that references
federatedGraphClients and federatedGraphPersistedOperations, either re-query the
client using a FOR UPDATE/row-lock (so no concurrent publishes can insert
children) or replace the separate select with a single delete on
federatedGraphPersistedOperations using RETURNING to collect deleted rows (id,
operationId, operationNames) and count, then delete the federatedGraphClients
row; return the captured returned rows and count (use the same variables client
and deletedOperations names to minimize changes).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67ee1d7c-d8bd-4bac-8370-cf7a0e5b2e19

📥 Commits

Reviewing files that changed from the base of the PR and between 3730a28 and 6c304eb.

⛔ Files ignored due to path filters (2)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • connect-go/gen/proto/wg/cosmo/platform/v1/platformv1connect/platform.connect.go is excluded by !**/gen/**
📒 Files selected for processing (15)
  • cli/src/commands/clients/commands/delete.ts
  • cli/src/commands/clients/index.ts
  • cli/test/clients-delete.test.ts
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/persisted-operation/deleteClient.ts
  • controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • docs-website/cli/clients.mdx
  • docs-website/cli/clients/delete.mdx
  • docs-website/docs.json
  • proto/wg/cosmo/platform/v1/platform.proto
✅ Files skipped from review due to trivial changes (4)
  • docs-website/cli/clients.mdx
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • docs-website/docs.json
  • cli/test/clients-delete.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • proto/wg/cosmo/platform/v1/platform.proto
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (4)
controlplane/src/core/repositories/OperationsRepository.ts (1)

387-425: ⚠️ Potential issue | 🟠 Major

Capture deleted operations atomically.

This reads child operations before deleting the client, but nothing prevents concurrent publishes in that window. A new operation can be inserted, cascaded away by the client delete, and still be missing from deletedOperationsCount and deletedOperations. Lock the client row first, or delete child rows explicitly with RETURNING inside the same transaction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` around lines 387
- 425, The select-before-delete is racy; instead capture deleted child
operations atomically within the transaction by deleting
federatedGraphPersistedOperations with a RETURNING clause (or by locking the
client row with FOR UPDATE before reading) and use the returned rows as
deletedOperations; then delete the federatedGraphClients row and return the
client info plus deletedOperationsCount and deletedOperations mapped from the
RETURNING results (referencing federatedGraphPersistedOperations,
federatedGraphClients, tx, and the deletedOperations variable to locate where to
change).
controlplane/src/core/bufservices/persisted-operation/deleteClient.ts (2)

91-103: ⚠️ Potential issue | 🟠 Major

Surface manifest regeneration failures instead of returning success.

If manifest upload fails here, the last published manifest can still reference operations that were just deleted, but the RPC still returns OK. That leaves router-facing persisted-operation state stale while the CLI reports success.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/bufservices/persisted-operation/deleteClient.ts` around
lines 91 - 103, The manifest regeneration failure is being logged but swallowed
in deleteClient; change the behavior in the catch after
operationsRepo.generateAndUploadManifest so the failure is propagated instead of
returning success—log the error (as currently done) and then rethrow the Error
(or return a failed RPC response) so callers see the failure; reference
operationsRepo.generateAndUploadManifest, the catch error variable,
logger.error, req.clientName, federatedGraph.id and authContext.organizationId
when making the change.

60-78: ⚠️ Potential issue | 🟠 Major

Blob cleanup should not run before the transactional delete.

removeDirectory() runs before operationsRepo.deleteClient(). If the DB transaction fails afterward, the client row can remain while its persisted-operation blobs are already gone. Move blob cleanup behind the committed DB delete, and use retry/compensation for post-commit failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/bufservices/persisted-operation/deleteClient.ts` around
lines 60 - 78, Currently the blob cleanup via opts.blobStorage.removeDirectory({
key: clientDirectory }) runs before the DB delete (operationsRepo.deleteClient),
risking orphaned DB rows if the transaction fails; move the removeDirectory call
to after the deleteClient call (i.e., perform opts.blobStorage.removeDirectory
only after operationsRepo.deleteClient completes successfully) and implement a
retry/compensation strategy for post-commit failures (catch errors from
removeDirectory after deleteClient, log with context including clientDirectory
and req.clientName, and retry a few times or record a remediation task to clean
up later).
proto/wg/cosmo/platform/v1/platform.proto (1)

2151-2155: ⚠️ Potential issue | 🟠 Major

Add a server-side confirmation precondition to DeleteClientRequest.

The final delete call still carries no force flag or expected-deletion precondition, so the server cannot verify that cascading persisted-operation deletion was acknowledged. Direct RPC callers can bypass the prompt entirely, and the preview/delete flow can race if new operations appear between the two calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proto/wg/cosmo/platform/v1/platform.proto` around lines 2151 - 2155, Update
the DeleteClientRequest proto to include a server-side precondition so the
server can verify the client's intent and prevent races; add an optional string
expected_deletion_token (or expected_resource_version) field to carry the
preview-run acknowledgement and an optional bool force field to allow bypass
when explicitly requested, then ensure downstream handlers that consume
DeleteClientRequest (look for DeleteClientRequest usages in RPC
handlers/services) validate expected_deletion_token against the persisted
preview token (or resource version) and only perform cascading deletes when it
matches or when force is true.
🧹 Nitpick comments (3)
cli/test/clients-list.test.ts (3)

66-68: Restore mocks in afterEach to avoid global spy leakage across tests.

Only resetting process.exitCode leaves console.log / process spies patched between tests.

♻️ Proposed fix
  afterEach(() => {
    process.exitCode = undefined;
+   vi.restoreAllMocks();
  });

Also applies to: 148-150

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-list.test.ts` around lines 66 - 68, The afterEach hook only
clears process.exitCode but leaves patched spies (like console.log and process)
active; update the afterEach in cli/test/clients-list.test.ts (the afterEach
block) to call jest.restoreAllMocks() (and optionally jest.resetAllMocks() if
you also want mock state cleared) so all spies/mocks are restored between tests
and no global leakage occurs; apply the same change to the other afterEach
instance around lines 148-150.

121-132: Add a test for thrown/rejected ConnectRPC failures (network/timeout/protocol).

Current error tests only cover response-level EnumStatusCode.ERR. A rejected RPC promise should also be covered to lock in robust CLI behavior.

Based on learnings ConnectRPC promise-client methods can throw/reject ConnectError for transport-level failures and should be handled via try-catch.

Also applies to: 200-218

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-list.test.ts` around lines 121 - 132, Add a test to cover
transport-level RPC failures by simulating the client promise rejecting with a
ConnectError (instead of returning a response with EnumStatusCode.ERR) and
asserting runList rejects and logs the error; specifically update the tests
around clients-list.test.ts (near the existing test using runList) to call
runList with a mocked client method that rejects with a ConnectError (or throws)
and then use await expect(runList(...)).rejects.toThrow(), plus
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(/* error message
*/)), expect(stderrSpy).toHaveBeenCalled(), and
expect(exitSpy).toHaveBeenCalled() so thrown/rejected ConnectRPC failures are
covered the same way as response-level errors.

11-26: Replace any and add explicit return types in test helpers.

onGetClients?: (req: any) => void and implicit helper return types weaken compile-time guarantees in this test harness.

♻️ Proposed fix
-import { GetClientsResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
+import { GetClientsRequest, GetClientsResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';

-function createMockTransport(response: PartialMessage<GetClientsResponse>, onGetClients?: (req: any) => void) {
+function createMockTransport(
+  response: PartialMessage<GetClientsResponse>,
+  onGetClients?: (req: GetClientsRequest) => void,
+): ReturnType<typeof createRouterTransport> {
   return createRouterTransport(({ service }) => {
     service(PlatformService, {
-      getClients: (req) => {
+      getClients: (req: GetClientsRequest) => {
         onGetClients?.(req);
         return response;
       },
     });
   });
 }

-function getJsonOutput(logSpy: MockInstance<typeof console.log>) {
+function getJsonOutput(logSpy: MockInstance<typeof console.log>): Record<string, unknown> {
   const call = logSpy.mock.calls.find(([arg]) => {
     try {
       JSON.parse(String(arg));
       return true;
     } catch {
       return false;
     }
   });

   if (!call) {
     throw new Error('No JSON output found in console.log calls');
   }

-  return JSON.parse(String(call[0]));
+  return JSON.parse(String(call[0])) as Record<string, unknown>;
 }

As per coding guidelines "Use explicit type annotations for function parameters and return types in TypeScript" and "Avoid any type in TypeScript; use specific types or generics".

Also applies to: 36-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-list.test.ts` around lines 11 - 26, The test helpers use an
implicit any and missing explicit return annotations; change the onGetClients
callback signatures in createMockTransport and runList from (req: any) => void
to a specific request type such as (req: GetClientsRequest) => void (importing
GetClientsRequest from the generated proto/types), and add explicit return types
for createMockTransport (e.g., annotate it with the transport type returned by
createRouterTransport or ReturnType<typeof createRouterTransport>) and for any
test helpers with implicit returns so all functions have explicit TypeScript
return annotations (e.g., Promise<void> for async helpers).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@controlplane/src/core/bufservices/persisted-operation/deleteClient.ts`:
- Around line 91-103: The manifest regeneration failure is being logged but
swallowed in deleteClient; change the behavior in the catch after
operationsRepo.generateAndUploadManifest so the failure is propagated instead of
returning success—log the error (as currently done) and then rethrow the Error
(or return a failed RPC response) so callers see the failure; reference
operationsRepo.generateAndUploadManifest, the catch error variable,
logger.error, req.clientName, federatedGraph.id and authContext.organizationId
when making the change.
- Around line 60-78: Currently the blob cleanup via
opts.blobStorage.removeDirectory({ key: clientDirectory }) runs before the DB
delete (operationsRepo.deleteClient), risking orphaned DB rows if the
transaction fails; move the removeDirectory call to after the deleteClient call
(i.e., perform opts.blobStorage.removeDirectory only after
operationsRepo.deleteClient completes successfully) and implement a
retry/compensation strategy for post-commit failures (catch errors from
removeDirectory after deleteClient, log with context including clientDirectory
and req.clientName, and retry a few times or record a remediation task to clean
up later).

In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 387-425: The select-before-delete is racy; instead capture deleted
child operations atomically within the transaction by deleting
federatedGraphPersistedOperations with a RETURNING clause (or by locking the
client row with FOR UPDATE before reading) and use the returned rows as
deletedOperations; then delete the federatedGraphClients row and return the
client info plus deletedOperationsCount and deletedOperations mapped from the
RETURNING results (referencing federatedGraphPersistedOperations,
federatedGraphClients, tx, and the deletedOperations variable to locate where to
change).

In `@proto/wg/cosmo/platform/v1/platform.proto`:
- Around line 2151-2155: Update the DeleteClientRequest proto to include a
server-side precondition so the server can verify the client's intent and
prevent races; add an optional string expected_deletion_token (or
expected_resource_version) field to carry the preview-run acknowledgement and an
optional bool force field to allow bypass when explicitly requested, then ensure
downstream handlers that consume DeleteClientRequest (look for
DeleteClientRequest usages in RPC handlers/services) validate
expected_deletion_token against the persisted preview token (or resource
version) and only perform cascading deletes when it matches or when force is
true.

---

Nitpick comments:
In `@cli/test/clients-list.test.ts`:
- Around line 66-68: The afterEach hook only clears process.exitCode but leaves
patched spies (like console.log and process) active; update the afterEach in
cli/test/clients-list.test.ts (the afterEach block) to call
jest.restoreAllMocks() (and optionally jest.resetAllMocks() if you also want
mock state cleared) so all spies/mocks are restored between tests and no global
leakage occurs; apply the same change to the other afterEach instance around
lines 148-150.
- Around line 121-132: Add a test to cover transport-level RPC failures by
simulating the client promise rejecting with a ConnectError (instead of
returning a response with EnumStatusCode.ERR) and asserting runList rejects and
logs the error; specifically update the tests around clients-list.test.ts (near
the existing test using runList) to call runList with a mocked client method
that rejects with a ConnectError (or throws) and then use await
expect(runList(...)).rejects.toThrow(), plus
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(/* error message
*/)), expect(stderrSpy).toHaveBeenCalled(), and
expect(exitSpy).toHaveBeenCalled() so thrown/rejected ConnectRPC failures are
covered the same way as response-level errors.
- Around line 11-26: The test helpers use an implicit any and missing explicit
return annotations; change the onGetClients callback signatures in
createMockTransport and runList from (req: any) => void to a specific request
type such as (req: GetClientsRequest) => void (importing GetClientsRequest from
the generated proto/types), and add explicit return types for
createMockTransport (e.g., annotate it with the transport type returned by
createRouterTransport or ReturnType<typeof createRouterTransport>) and for any
test helpers with implicit returns so all functions have explicit TypeScript
return annotations (e.g., Promise<void> for async helpers).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d66eb88d-ac6a-4419-b9b8-f9c905494ce9

📥 Commits

Reviewing files that changed from the base of the PR and between 6c304eb and 12dd134.

⛔ Files ignored due to path filters (2)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • connect-go/gen/proto/wg/cosmo/platform/v1/platformv1connect/platform.connect.go is excluded by !**/gen/**
📒 Files selected for processing (18)
  • cli/src/commands/clients/commands/delete.ts
  • cli/src/commands/clients/commands/list.ts
  • cli/src/commands/clients/index.ts
  • cli/test/clients-delete.test.ts
  • cli/test/clients-list.test.ts
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/persisted-operation/deleteClient.ts
  • controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • docs-website/cli/clients.mdx
  • docs-website/cli/clients/delete.mdx
  • docs-website/cli/clients/list.mdx
  • docs-website/docs.json
  • proto/wg/cosmo/platform/v1/platform.proto
✅ Files skipped from review due to trivial changes (5)
  • docs-website/docs.json
  • docs-website/cli/clients.mdx
  • docs-website/cli/clients/list.mdx
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/test/persisted-operations.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • cli/src/commands/clients/index.ts
  • controlplane/src/core/bufservices/PlatformService.ts
  • cli/test/clients-delete.test.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • cli/src/commands/clients/commands/list.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
cli/test/clients-list.test.ts (1)

121-132: Add a transport-failure test where getClients rejects/throws.

Current error coverage validates an error-shaped response body, but not the transport exception path (network/timeout/protocol rejection). The command correctly handles both scenarios via try-catch in fetchClients (lines 41-61 of list.ts), but the catch block is never exercised by the test suite. Add one case that makes getClients reject to verify that branch is properly handled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-list.test.ts` around lines 121 - 132, Add a new test case
that simulates a transport failure by making getClients reject so the catch
block in fetchClients (in list.ts) is exercised: call your test helper runList
with a stubbed client/getClients that returns Promise.reject(new Error('network
error')) (or rejects) and assert the CLI logs the error
(expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('network error'))),
writes to stderr and calls exit, mirroring the existing "fails on rpc error"
assertions; reference getClients, fetchClients and runList when locating code to
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/commands/clients/commands/delete.ts`:
- Line 123: The --namespace option currently uses Commander's optional syntax in
command.option('-n, --namespace [string]', ...) which allows the flag to be
passed without a value and yields a boolean; change it to require a value by
using the required-value syntax command.option('-n, --namespace <string>', 'The
namespace of the federated graph or monograph.', 'default') so Commander
enforces a string is provided for namespace (update any related validation/usage
in the delete command handler if needed).

In `@cli/test/clients-list.test.ts`:
- Around line 66-68: The afterEach currently only resets process.exitCode but
leaves global spies (console.log, process.stderr.write, process.exit) active;
update the afterEach block to also restore those spies by calling
jest.restoreAllMocks() (or the appropriate test framework restore method) so
global spies are removed, and still reset process.exitCode; reference the
afterEach function and the globals console.log, process.stderr.write,
process.exit, and process.exitCode when making the change.
- Around line 36-51: The helper getJsonOutput lacks an explicit TypeScript
return type; change its signature to declare a return type (preferably a generic
like <T = unknown> so callers can assert structure) e.g. getJsonOutput<T =
unknown>(logSpy: MockInstance<typeof console.log>): T, and cast the final parsed
value to T when returning (the JSON.parse(String(call[0])) expression). This
keeps callers' assertions and refactors safe while preserving the existing logic
that finds and parses the console.log call.
- Around line 11-26: The test currently types the mocked callback as
onGetClients?: (req: any) => void which removes compile-time safety; change the
parameter type to the real request type (e.g. GetClientsRequest) in both
createMockTransport and runList signatures and usages (replace any with
GetClientsRequest), and add the corresponding import from the generated proto
types so the mock callback enforces the correct request shape for getClients.

In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 371-421: The snapshot returned by findFirst() can be stale if
another transaction deletes the client before the FOR UPDATE is acquired; after
the FOR UPDATE select (the
tx.select(...).from(federatedGraphClients)...for('update') call) re-check that
the row still exists in the same transaction (e.g. select
federatedGraphClients.id by federatedGraphId and client.id) and if that check
returns no row, return undefined instead of proceeding to delete or returning
the old client; ensure you perform this existence check using the same tx and
the federatedGraphClients + client.id identifiers so the method returns
undefined when another delete won the race.

---

Nitpick comments:
In `@cli/test/clients-list.test.ts`:
- Around line 121-132: Add a new test case that simulates a transport failure by
making getClients reject so the catch block in fetchClients (in list.ts) is
exercised: call your test helper runList with a stubbed client/getClients that
returns Promise.reject(new Error('network error')) (or rejects) and assert the
CLI logs the error
(expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('network error'))),
writes to stderr and calls exit, mirroring the existing "fails on rpc error"
assertions; reference getClients, fetchClients and runList when locating code to
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1fc1eb6d-9af1-46a4-8e1f-d60fa0a5269d

📥 Commits

Reviewing files that changed from the base of the PR and between 12dd134 and 326e1bf.

⛔ Files ignored due to path filters (2)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • connect-go/gen/proto/wg/cosmo/platform/v1/platformv1connect/platform.connect.go is excluded by !**/gen/**
📒 Files selected for processing (19)
  • cli/src/commands/clients/commands/delete.ts
  • cli/src/commands/clients/commands/list.ts
  • cli/src/commands/clients/index.ts
  • cli/src/commands/index.ts
  • cli/test/clients-delete.test.ts
  • cli/test/clients-list.test.ts
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/persisted-operation/deleteClient.ts
  • controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • docs-website/cli/clients.mdx
  • docs-website/cli/clients/delete.mdx
  • docs-website/cli/clients/list.mdx
  • docs-website/docs.json
  • proto/wg/cosmo/platform/v1/platform.proto
✅ Files skipped from review due to trivial changes (6)
  • docs-website/docs.json
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • docs-website/cli/clients.mdx
  • cli/src/commands/index.ts
  • cli/src/commands/clients/commands/list.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • cli/src/commands/clients/index.ts
  • docs-website/cli/clients/list.mdx
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/test/persisted-operations.test.ts
  • cli/test/clients-delete.test.ts

Comment thread cli/src/commands/clients/commands/delete.ts Outdated
Comment thread cli/test/clients-list.test.ts Outdated
Comment thread cli/test/clients-list.test.ts
Comment thread cli/test/clients-list.test.ts
Comment thread controlplane/src/core/repositories/OperationsRepository.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
cli/src/commands/clients/index.ts (1)

7-7: Add an explicit return type on the exported command factory.

Line 7 omits the return type, which weakens contract clarity for command composition.

Suggested patch
-export default (opts: BaseCommandOptions) => {
+export default (opts: BaseCommandOptions): Command => {

As per coding guidelines: “Use explicit type annotations for function parameters and return types in TypeScript”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/commands/clients/index.ts` at line 7, The exported default arrow
function (export default (opts: BaseCommandOptions) => { ... }) lacks an
explicit return type; update its signature to include the correct return type
(e.g., the project's CommandFactory/CommandModule type or the specific command
interface used across CLI commands) so callers and TypeScript know the
contract—modify the export default (...) declaration to add the explicit return
type while keeping the existing opts: BaseCommandOptions parameter name and
implementation unchanged.
cli/test/clients-list.test.ts (1)

56-71: Consider deduplicating repeated spy setup/teardown.

Both suites repeat identical beforeEach/afterEach logic; extracting a small helper would reduce maintenance noise.

Possible refactor
+function setupProcessSpies() {
+  const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
+  const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
+  const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
+    throw new Error('process.exit');
+  });
+  return { logSpy, stderrSpy, exitSpy };
+}

Also applies to: 143-153

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/test/clients-list.test.ts` around lines 56 - 71, Extract the repeated spy
setup/teardown into a shared test helper and call it from both suites instead of
duplicating code: move the vi.spyOn calls for console.log, process.stderr.write
and process.exit (creating logSpy, stderrSpy, exitSpy) and the afterEach reset
of process.exitCode into a reusable function (e.g., setupStdSpies or
useStdSpies) and invoke that in the describe blocks that currently declare
beforeEach/afterEach; ensure the helper returns or exposes the spies if the
tests rely on logSpy/stderrSpy/exitSpy so existing tests referencing those
symbols keep working.
controlplane/test/persisted-operations.test.ts (1)

604-634: Consider adding an explicit RBAC denial test for deleteClient.

You already cover unauthorized preview. Adding the same viewer-role denial case for deleteClient would close the auth parity gap for the new RPC.

Also applies to: 636-694

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/persisted-operations.test.ts` around lines 604 - 634, Add a
parallel test that asserts a user with the viewer role is denied from actually
calling deleteClient (mirroring the preview test): reuse the test setup from
SetupTest and setupFederatedGraph, switch identity via
authenticator.changeUserWithSuppliedContext using
users[TestUser.viewerTimCompanyA] with createTestRBACEvaluator(createTestGroup({
role: 'namespace-viewer' })), then call client.deleteClient({ fedGraphName,
namespace: 'default', clientName: 'curl' }) and assert the response code equals
EnumStatusCode.ERROR_NOT_AUTHORIZED; place this alongside the existing
previewDeleteClient test to ensure auth parity.
controlplane/src/core/repositories/OperationsRepository.ts (1)

46-50: Prefer an interface for this DTO shape.

This is a plain object contract, so using an interface here would match the repository’s TypeScript convention better.

♻️ Suggested change
-type DeletedClientOperationDTO = {
+interface DeletedClientOperationDTO {
   id: string;
   operationId: string;
   operationNames: string[];
-};
+}
As per coding guidelines, `**/*.{ts,tsx}`: Prefer interfaces over type aliases for object shapes in TypeScript.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` around lines 46 -
50, Replace the type alias DeletedClientOperationDTO with an equivalent
interface to follow project convention: declare an interface named
DeletedClientOperationDTO with properties id: string, operationId: string, and
operationNames: string[] and update any imports/uses if needed; keep the exact
property names and types so consumers of the repository (references to
DeletedClientOperationDTO) remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/test/clients-list.test.ts`:
- Around line 124-130: The test currently uses expect(...).rejects.toThrow()
which allows any thrown error; tighten it to assert the specific sentinel error
thrown by the process.exit mock in runList so the test only passes for the
intended RPC error path — replace rejects.toThrow() with rejects.toThrow(/* the
sentinel error string or Error instance your process.exit mock throws */) or
rejects.toMatchObject(/* the sentinel Error object */), referencing runList and
the EnumStatusCode.ERR response to ensure you assert the exact mocked error
produced by the process.exit branch.

In `@controlplane/test/persisted-operations.test.ts`:
- Around line 785-826: The test "Should skip blob storage removal when deleting
a client without persisted operations" currently never asserts the blob removal
behavior; update it to assert that blobStorage.removeDirectory is not invoked
after calling client.deleteClient. Locate the test body (the one using SetupTest
to get { client, server, blobStorage }) and add an expectation referencing that
blobStorage mock: e.g. verify blobStorage.removeDirectory was not called
(expect(blobStorage.removeDirectory).not.toHaveBeenCalled()) after the
deleteClient call and before closing the server, so the test title matches its
assertions.

---

Nitpick comments:
In `@cli/src/commands/clients/index.ts`:
- Line 7: The exported default arrow function (export default (opts:
BaseCommandOptions) => { ... }) lacks an explicit return type; update its
signature to include the correct return type (e.g., the project's
CommandFactory/CommandModule type or the specific command interface used across
CLI commands) so callers and TypeScript know the contract—modify the export
default (...) declaration to add the explicit return type while keeping the
existing opts: BaseCommandOptions parameter name and implementation unchanged.

In `@cli/test/clients-list.test.ts`:
- Around line 56-71: Extract the repeated spy setup/teardown into a shared test
helper and call it from both suites instead of duplicating code: move the
vi.spyOn calls for console.log, process.stderr.write and process.exit (creating
logSpy, stderrSpy, exitSpy) and the afterEach reset of process.exitCode into a
reusable function (e.g., setupStdSpies or useStdSpies) and invoke that in the
describe blocks that currently declare beforeEach/afterEach; ensure the helper
returns or exposes the spies if the tests rely on logSpy/stderrSpy/exitSpy so
existing tests referencing those symbols keep working.

In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 46-50: Replace the type alias DeletedClientOperationDTO with an
equivalent interface to follow project convention: declare an interface named
DeletedClientOperationDTO with properties id: string, operationId: string, and
operationNames: string[] and update any imports/uses if needed; keep the exact
property names and types so consumers of the repository (references to
DeletedClientOperationDTO) remain unchanged.

In `@controlplane/test/persisted-operations.test.ts`:
- Around line 604-634: Add a parallel test that asserts a user with the viewer
role is denied from actually calling deleteClient (mirroring the preview test):
reuse the test setup from SetupTest and setupFederatedGraph, switch identity via
authenticator.changeUserWithSuppliedContext using
users[TestUser.viewerTimCompanyA] with createTestRBACEvaluator(createTestGroup({
role: 'namespace-viewer' })), then call client.deleteClient({ fedGraphName,
namespace: 'default', clientName: 'curl' }) and assert the response code equals
EnumStatusCode.ERROR_NOT_AUTHORIZED; place this alongside the existing
previewDeleteClient test to ensure auth parity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 06119a7a-8f3c-4bbf-b45a-adb431b4f181

📥 Commits

Reviewing files that changed from the base of the PR and between 326e1bf and a4046e7.

⛔ Files ignored due to path filters (2)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • connect-go/gen/proto/wg/cosmo/platform/v1/platformv1connect/platform.connect.go is excluded by !**/gen/**
📒 Files selected for processing (18)
  • cli/src/commands/clients/commands/delete.ts
  • cli/src/commands/clients/commands/list.ts
  • cli/src/commands/clients/index.ts
  • cli/test/clients-delete.test.ts
  • cli/test/clients-list.test.ts
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • connect/src/wg/cosmo/platform/v1/platform_connect.ts
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/src/core/bufservices/PlatformService.ts
  • controlplane/src/core/bufservices/persisted-operation/deleteClient.ts
  • controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/test/persisted-operations.test.ts
  • docs-website/cli/clients.mdx
  • docs-website/cli/clients/delete.mdx
  • docs-website/cli/clients/list.mdx
  • docs-website/docs.json
  • proto/wg/cosmo/platform/v1/platform.proto
✅ Files skipped from review due to trivial changes (2)
  • docs-website/cli/clients.mdx
  • controlplane/src/core/bufservices/persisted-operation/previewDeleteClient.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • docs-website/docs.json
  • controlplane/src/core/bufservices/PlatformService.ts
  • docs-website/cli/clients/list.mdx
  • connect/src/wg/cosmo/platform/v1/platform-PlatformService_connectquery.ts
  • cli/src/commands/clients/commands/list.ts
  • cli/src/commands/clients/commands/delete.ts
  • cli/test/clients-delete.test.ts

Comment thread cli/test/clients-list.test.ts
Comment thread controlplane/test/persisted-operations.test.ts
@comatory comatory marked this pull request as ready for review April 28, 2026 12:16
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant