Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/cli/src/commands/workspaces/update/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CLIError, positional, string } from "@superset/cli-framework";
import { command } from "../../../lib/command";

export default command({
description: "Update a workspace",
args: [positional("id").required().desc("Workspace UUID")],
options: {
name: string().desc("Workspace name"),
},
run: async ({ ctx, args, options }) => {
const id = args.id as string;
const organizationId = ctx.config.organizationId;
if (!organizationId) {
throw new CLIError("No active organization", "Run: superset auth login");
}

if (options.name === undefined) {
throw new CLIError("No fields to update", "Pass --name <new-name>");
}

const updated = await ctx.api.v2Workspace.update.mutate({
id,
name: options.name,
});
Comment on lines +17 to +24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject blank workspace names before calling the API.

Line 17 only checks undefined, so empty/whitespace values can still be sent at Line 23. Add a trimmed non-empty check for better CLI-side validation.

Proposed patch
-		if (options.name === undefined) {
+		const name = options.name?.trim();
+		if (!name) {
 			throw new CLIError("No fields to update", "Pass --name <new-name>");
 		}
@@
 		const updated = await ctx.api.v2Workspace.update.mutate({
 			id,
-			name: options.name,
+			name,
 		});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (options.name === undefined) {
throw new CLIError("No fields to update", "Pass --name <new-name>");
}
const updated = await ctx.api.v2Workspace.update.mutate({
id,
name: options.name,
});
const name = options.name?.trim();
if (!name) {
throw new CLIError("No fields to update", "Pass --name <new-name>");
}
const updated = await ctx.api.v2Workspace.update.mutate({
id,
name,
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/workspaces/update/command.ts` around lines 17 - 24,
The CLI currently only rejects undefined names but allows empty or
whitespace-only names to be sent to ctx.api.v2Workspace.update.mutate; update
the validation around options.name (the block that throws CLIError) to trim the
value and ensure it's non-empty (e.g., check that options.name?.trim() is
truthy) and throw the same CLIError with the "Pass --name <new-name>" hint when
invalid, before calling ctx.api.v2Workspace.update.mutate.


return {
data: updated,
message: `Updated workspace ${id}`,
};
},
});
2 changes: 2 additions & 0 deletions packages/mcp-v2/src/tools/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as tasksUpdate from "./tasks/update";
import * as workspacesCreate from "./workspaces/create";
import * as workspacesDelete from "./workspaces/delete";
import * as workspacesList from "./workspaces/list";
import * as workspacesUpdate from "./workspaces/update";

const REGISTRARS = [
tasksList,
Expand All @@ -47,6 +48,7 @@ const REGISTRARS = [
automationsLogs,
workspacesList,
workspacesCreate,
workspacesUpdate,
workspacesDelete,
agentsRun,
agentsList,
Expand Down
20 changes: 20 additions & 0 deletions packages/mcp-v2/src/tools/workspaces/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { createMcpCaller } from "../../caller";
import { defineTool } from "../../define-tool";

export function register(server: McpServer): void {
defineTool(server, {
name: "workspaces_update",
description:
"Update fields on an existing workspace. At least one field is required.",
inputSchema: {
id: z.string().uuid().describe("Workspace UUID."),
name: z.string().min(1).optional().describe("New workspace name."),
},
handler: async (input, ctx) => {
const caller = createMcpCaller(ctx);
return caller.v2Workspace.update(input);
},
Comment on lines +15 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 No client-side guard against an all-optional payload. The CLI throws "No fields to update" before touching the network; without the same check here the MCP handler forwards { id } to the server and returns an opaque cloud error instead of a clear message. Adding a pre-flight check mirrors the CLI's behaviour.

Suggested change
handler: async (input, ctx) => {
const caller = createMcpCaller(ctx);
return caller.v2Workspace.update(input);
},
handler: async (input, ctx) => {
const { id, ...fields } = input;
if (Object.keys(fields).length === 0) {
throw new Error("No fields to update. Pass at least one field such as name.");
}
const caller = createMcpCaller(ctx);
return caller.v2Workspace.update(input);
},
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/mcp-v2/src/tools/workspaces/update.ts
Line: 15-18

Comment:
No client-side guard against an all-optional payload. The CLI throws "No fields to update" before touching the network; without the same check here the MCP handler forwards `{ id }` to the server and returns an opaque cloud error instead of a clear message. Adding a pre-flight check mirrors the CLI's behaviour.

```suggestion
		handler: async (input, ctx) => {
			const { id, ...fields } = input;
			if (Object.keys(fields).length === 0) {
				throw new Error("No fields to update. Pass at least one field such as name.");
			}
			const caller = createMcpCaller(ctx);
			return caller.v2Workspace.update(input);
		},
```

How can I resolve this? If you propose a fix, please make it concise.

});
}
41 changes: 41 additions & 0 deletions packages/sdk/src/resources/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ export class Workspaces extends APIResource {
);
}

/**
* Update fields on a workspace. At least one field is required. Currently
* only `name` is exposed — branch and host moves require host-side
* orchestration and aren't safe to set directly.
*
* Mirrors `superset workspaces update`.
*/
update(
id: string,
params: WorkspaceUpdateParams,
options?: RequestOptions,
): APIPromise<WorkspaceUpdateResult> {
return this._client.mutation<WorkspaceUpdateResult>(
"v2Workspace.update",
{ id, ...params },
options,
);
}

/**
* Delete a workspace by id. Looks up the host the workspace lives on (via
* the cloud index) and routes the delete to that host's service through
Expand Down Expand Up @@ -179,6 +198,26 @@ export interface WorkspaceCreateResult {
alreadyExists: boolean;
}

export interface WorkspaceUpdateParams {
/** New workspace name. */
name?: string;
}
Comment on lines +201 to +204
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Empty-object update compiles cleanly but fails at runtime. WorkspaceUpdateParams has only optional fields, so client.workspaces.update(id, {}) passes the TypeScript compiler and then hits the cloud with nothing to change, returning a server-side BAD_REQUEST. A RequireAtLeastOne constraint makes the invalid call a compile-time error rather than a silent runtime failure.

Suggested change
export interface WorkspaceUpdateParams {
/** New workspace name. */
name?: string;
}
/** Requires at least one field — an all-optional update is rejected by the server. */
export type WorkspaceUpdateParams = RequireAtLeastOne<{
/** New workspace name. */
name?: string;
}>;
/** Utility: at least one key of T must be present. */
type RequireAtLeastOne<T> = {
[K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
}[keyof T];
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/sdk/src/resources/workspaces.ts
Line: 201-204

Comment:
Empty-object update compiles cleanly but fails at runtime. `WorkspaceUpdateParams` has only optional fields, so `client.workspaces.update(id, {})` passes the TypeScript compiler and then hits the cloud with nothing to change, returning a server-side BAD_REQUEST. A `RequireAtLeastOne` constraint makes the invalid call a compile-time error rather than a silent runtime failure.

```suggestion
/** Requires at least one field — an all-optional update is rejected by the server. */
export type WorkspaceUpdateParams = RequireAtLeastOne<{
	/** New workspace name. */
	name?: string;
}>;

/** Utility: at least one key of T must be present. */
type RequireAtLeastOne<T> = {
	[K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
}[keyof T];
```

How can I resolve this? If you propose a fix, please make it concise.


export interface WorkspaceUpdateResult {
id: string;
name: string;
branch: string;
organizationId: string;
projectId: string;
hostId: string;
type: "main" | "worktree";
createdByUserId: string | null;
taskId: string | null;
createdAt: Date;
updatedAt: Date;
txid: number;
}

export interface WorkspaceDeleteResult {
[key: string]: unknown;
}
Expand All @@ -193,6 +232,8 @@ export declare namespace Workspaces {
WorkspaceAgentLaunch,
WorkspaceCreateAgentResult,
WorkspaceCreateResult,
WorkspaceUpdateParams,
WorkspaceUpdateResult,
WorkspaceDeleteResult,
};
}
1 change: 1 addition & 0 deletions skills/superset/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ If `$SUPERSET_WORKSPACE_ID` is unset, you're not inside a Superset workspace —
superset workspaces create --project <id> --host <id> --name "..." --branch <branch>
superset workspaces create --project <id> --local --name "..." --pr <number>
superset workspaces list [--host <id> | --local]
superset workspaces update <id> --name "..."
superset workspaces delete <id> [<id>...]
```

Expand Down
Loading