From ac6a4ff9670f10d1237f94ae41f04bfa00139394 Mon Sep 17 00:00:00 2001 From: Daniel Moerner Date: Mon, 20 Apr 2026 16:03:38 -0400 Subject: [PATCH 1/3] fix(config): Normalize keys and improve help text Agents got confused and were trying to use comma-separated individual keys. Specify in the help texts that these are handler keys, and allow comma-separated keys. --- packages/cli-core/src/cli-program.ts | 12 +++++----- .../cli-core/src/commands/config/README.md | 8 +++---- .../cli-core/src/commands/config/pull.test.ts | 22 +++++++++++++++++-- .../src/commands/config/schema.test.ts | 22 +++++++++++++++++-- packages/cli-core/src/lib/plapi.ts | 22 ++++++++++--------- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/packages/cli-core/src/cli-program.ts b/packages/cli-core/src/cli-program.ts index 1a636b0b..61753693 100644 --- a/packages/cli-core/src/cli-program.ts +++ b/packages/cli-core/src/cli-program.ts @@ -278,8 +278,8 @@ export function createProgram() { { command: "clerk config pull --output config.json", description: "Save config to a file" }, { command: "clerk config schema", description: "Print full config schema" }, { - command: "clerk config schema --keys social_login", - description: "Schema for specific keys", + command: "clerk config schema --keys auth_email session", + description: "Schema for specific top-level keys", }, { command: "clerk config patch --file config.json", @@ -309,7 +309,7 @@ export function createProgram() { .option("--app ", "Application ID to target (works from any directory)") .option("--instance ", "Instance to target (dev, prod, or a full instance ID)") .option("--output ", "Write config to a file instead of stdout") - .option("--keys ", "Config keys to retrieve") + .option("--keys ", "Top-level config keys to retrieve (e.g. auth_email, session)") .setExamples([ { command: "clerk config pull", description: "Print dev config to stdout" }, { command: "clerk config pull --instance prod", description: "Pull production config" }, @@ -323,12 +323,12 @@ export function createProgram() { .option("--app ", "Application ID to target (works from any directory)") .option("--instance ", "Instance to target (dev, prod, or a full instance ID)") .option("--output ", "Write schema to a file instead of stdout") - .option("--keys ", "Config keys to retrieve schema for") + .option("--keys ", "Top-level schema sections to retrieve (e.g. auth_email, session)") .setExamples([ { command: "clerk config schema", description: "Print full config schema" }, { - command: "clerk config schema --keys social_login", - description: "Schema for specific keys", + command: "clerk config schema --keys auth_email session", + description: "Schema for specific top-level keys", }, { command: "clerk config schema --output schema.json", description: "Save schema to a file" }, ]) diff --git a/packages/cli-core/src/commands/config/README.md b/packages/cli-core/src/commands/config/README.md index c12a1cea..075e464f 100644 --- a/packages/cli-core/src/commands/config/README.md +++ b/packages/cli-core/src/commands/config/README.md @@ -13,7 +13,7 @@ clerk config pull clerk config pull --app app_123 clerk config pull --instance prod clerk config pull --output clerk-config.json -clerk config pull --keys session sign_up +clerk config pull --keys auth_email session ``` #### Options @@ -23,7 +23,7 @@ clerk config pull --keys session sign_up | `--app ` | Application ID to target directly (works from any directory) | | `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | | `--output ` | Write config to a file instead of stdout | -| `--keys ` | Config keys to retrieve | +| `--keys ` | Top-level config keys to retrieve (e.g. auth_email, session) | #### Requirements @@ -51,7 +51,7 @@ clerk config schema clerk config schema --app app_123 clerk config schema --instance prod clerk config schema --output config-schema.json -clerk config schema --keys session sign_up +clerk config schema --keys auth_email session ``` #### Options @@ -61,7 +61,7 @@ clerk config schema --keys session sign_up | `--app ` | Application ID to target directly (works from any directory) | | `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | | `--output ` | Write schema to a file instead of stdout | -| `--keys ` | Config keys to retrieve schema for | +| `--keys ` | Top-level schema sections to retrieve (e.g. auth_email, session) | #### Requirements diff --git a/packages/cli-core/src/commands/config/pull.test.ts b/packages/cli-core/src/commands/config/pull.test.ts index 678f4b7e..9fc24fd7 100644 --- a/packages/cli-core/src/commands/config/pull.test.ts +++ b/packages/cli-core/src/commands/config/pull.test.ts @@ -258,9 +258,27 @@ describe("config pull", () => { instances: { development: "ins_dev" }, }); - await runConfigPull({ keys: ["session", "sign_up"] }); + await runConfigPull({ keys: ["session", "auth_email"] }); expect(requestedUrl).toContain("keys=session"); - expect(requestedUrl).toContain("keys=sign_up"); + expect(requestedUrl).toContain("keys=auth_email"); + }); + + test("splits comma-separated --keys into individual params", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify({ session: { lifetime: 604800 } }), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigPull({ keys: ["session,auth_email"] }); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); }); test("handles API errors gracefully", async () => { diff --git a/packages/cli-core/src/commands/config/schema.test.ts b/packages/cli-core/src/commands/config/schema.test.ts index 2d9819c3..ffb2433e 100644 --- a/packages/cli-core/src/commands/config/schema.test.ts +++ b/packages/cli-core/src/commands/config/schema.test.ts @@ -208,12 +208,30 @@ describe("config schema", () => { instances: { development: "ins_dev" }, }); - await runConfigSchema({ keys: ["session", "sign_up"] }); + await runConfigSchema({ keys: ["session", "auth_email"] }); expect(requestedUrl).toContain("keys=session"); - expect(requestedUrl).toContain("keys=sign_up"); + expect(requestedUrl).toContain("keys=auth_email"); expect(requestedUrl).toContain("/config/schema"); }); + test("splits comma-separated --keys into individual params", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify(mockSchema), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigSchema({ keys: ["session,auth_email"] }); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); + }); + test("errors when production instance not configured", async () => { await setProfile(process.cwd(), { workspaceId: "org_1", diff --git a/packages/cli-core/src/lib/plapi.ts b/packages/cli-core/src/lib/plapi.ts index 84336002..a4b50830 100644 --- a/packages/cli-core/src/lib/plapi.ts +++ b/packages/cli-core/src/lib/plapi.ts @@ -80,6 +80,16 @@ async function plapiFetch(method: string, url: URL, init?: { body?: string }): P return response; } +/** Normalize and append `keys` query params, splitting comma-separated values. */ +function appendKeys(url: URL, keys?: string[]): void { + if (!keys?.length) return; + for (const key of keys) { + for (const k of key.split(",")) { + if (k) url.searchParams.append("keys", k); + } + } +} + export async function fetchInstanceConfigSchema( applicationId: string, instanceId: string, @@ -89,11 +99,7 @@ export async function fetchInstanceConfigSchema( `/v1/platform/applications/${applicationId}/instances/${instanceId}/config/schema`, getPlapiBaseUrl(), ); - if (keys?.length) { - for (const key of keys) { - url.searchParams.append("keys", key); - } - } + appendKeys(url, keys); const response = await plapiFetch("GET", url); return response.json() as Promise>; } @@ -107,11 +113,7 @@ export async function fetchInstanceConfig( `/v1/platform/applications/${applicationId}/instances/${instanceId}/config`, getPlapiBaseUrl(), ); - if (keys?.length) { - for (const key of keys) { - url.searchParams.append("keys", key); - } - } + appendKeys(url, keys); const response = await plapiFetch("GET", url); return response.json() as Promise>; } From 9adb49278fa269bd72e7c97f3f5ba3ce30031aec Mon Sep 17 00:00:00 2001 From: Daniel Moerner Date: Mon, 20 Apr 2026 16:03:38 -0400 Subject: [PATCH 2/3] chore: add changeset --- ...onsider-whether-config-schema-keys-should-take-a-comma.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/aie-866-consider-whether-config-schema-keys-should-take-a-comma.md diff --git a/.changeset/aie-866-consider-whether-config-schema-keys-should-take-a-comma.md b/.changeset/aie-866-consider-whether-config-schema-keys-should-take-a-comma.md new file mode 100644 index 00000000..f279ea2c --- /dev/null +++ b/.changeset/aie-866-consider-whether-config-schema-keys-should-take-a-comma.md @@ -0,0 +1,5 @@ +--- +"clerk": patch +--- + +Accept comma-separated values for `--keys` in `config pull` and `config schema`, and clarify that keys refer to top-level config sections. From 82b1767ab3348103baf472d90c298064c4fbe882 Mon Sep 17 00:00:00 2001 From: Daniel Moerner Date: Mon, 20 Apr 2026 16:38:33 -0400 Subject: [PATCH 3/3] fix: Review suggestions (trimming, test coverage) --- packages/cli-core/src/cli-program.ts | 10 ++++++-- .../cli-core/src/commands/config/README.md | 24 +++++++++---------- .../cli-core/src/commands/config/pull.test.ts | 20 ++++++++++++++++ .../src/commands/config/schema.test.ts | 22 +++++++++++++++++ packages/cli-core/src/lib/plapi.ts | 3 ++- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/packages/cli-core/src/cli-program.ts b/packages/cli-core/src/cli-program.ts index 61753693..28198283 100644 --- a/packages/cli-core/src/cli-program.ts +++ b/packages/cli-core/src/cli-program.ts @@ -309,7 +309,10 @@ export function createProgram() { .option("--app ", "Application ID to target (works from any directory)") .option("--instance ", "Instance to target (dev, prod, or a full instance ID)") .option("--output ", "Write config to a file instead of stdout") - .option("--keys ", "Top-level config keys to retrieve (e.g. auth_email, session)") + .option( + "--keys ", + "Top-level config keys to retrieve, separated by spaces or commas (e.g. auth_email session)", + ) .setExamples([ { command: "clerk config pull", description: "Print dev config to stdout" }, { command: "clerk config pull --instance prod", description: "Pull production config" }, @@ -323,7 +326,10 @@ export function createProgram() { .option("--app ", "Application ID to target (works from any directory)") .option("--instance ", "Instance to target (dev, prod, or a full instance ID)") .option("--output ", "Write schema to a file instead of stdout") - .option("--keys ", "Top-level schema sections to retrieve (e.g. auth_email, session)") + .option( + "--keys ", + "Top-level schema sections to retrieve, separated by spaces or commas (e.g. auth_email session)", + ) .setExamples([ { command: "clerk config schema", description: "Print full config schema" }, { diff --git a/packages/cli-core/src/commands/config/README.md b/packages/cli-core/src/commands/config/README.md index 075e464f..c64995a4 100644 --- a/packages/cli-core/src/commands/config/README.md +++ b/packages/cli-core/src/commands/config/README.md @@ -18,12 +18,12 @@ clerk config pull --keys auth_email session #### Options -| Flag | Description | -| ------------------ | ----------------------------------------------------------------------------------- | -| `--app ` | Application ID to target directly (works from any directory) | -| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | -| `--output ` | Write config to a file instead of stdout | -| `--keys ` | Top-level config keys to retrieve (e.g. auth_email, session) | +| Flag | Description | +| ------------------ | ------------------------------------------------------------------------------------------ | +| `--app ` | Application ID to target directly (works from any directory) | +| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | +| `--output ` | Write config to a file instead of stdout | +| `--keys ` | Top-level config keys to retrieve, separated by spaces or commas (e.g. auth_email session) | #### Requirements @@ -56,12 +56,12 @@ clerk config schema --keys auth_email session #### Options -| Flag | Description | -| ------------------ | ----------------------------------------------------------------------------------- | -| `--app ` | Application ID to target directly (works from any directory) | -| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | -| `--output ` | Write schema to a file instead of stdout | -| `--keys ` | Top-level schema sections to retrieve (e.g. auth_email, session) | +| Flag | Description | +| ------------------ | ---------------------------------------------------------------------------------------------- | +| `--app ` | Application ID to target directly (works from any directory) | +| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | +| `--output ` | Write schema to a file instead of stdout | +| `--keys ` | Top-level schema sections to retrieve, separated by spaces or commas (e.g. auth_email session) | #### Requirements diff --git a/packages/cli-core/src/commands/config/pull.test.ts b/packages/cli-core/src/commands/config/pull.test.ts index 9fc24fd7..702d7527 100644 --- a/packages/cli-core/src/commands/config/pull.test.ts +++ b/packages/cli-core/src/commands/config/pull.test.ts @@ -281,6 +281,26 @@ describe("config pull", () => { expect(requestedUrl).toContain("keys=auth_email"); }); + test("trims whitespace from comma-separated --keys", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify({ session: { lifetime: 604800 } }), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigPull({ keys: ["session, auth_email"] }); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); + expect(requestedUrl).not.toContain("keys=+"); + expect(requestedUrl).not.toContain("keys=%20"); + }); + test("handles API errors gracefully", async () => { stubFetch(async () => new Response("Unauthorized", { status: 401 })); diff --git a/packages/cli-core/src/commands/config/schema.test.ts b/packages/cli-core/src/commands/config/schema.test.ts index ffb2433e..89f1d67a 100644 --- a/packages/cli-core/src/commands/config/schema.test.ts +++ b/packages/cli-core/src/commands/config/schema.test.ts @@ -228,8 +228,30 @@ describe("config schema", () => { }); await runConfigSchema({ keys: ["session,auth_email"] }); + expect(requestedUrl).toContain("/config/schema"); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); + }); + + test("trims whitespace from comma-separated --keys", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify(mockSchema), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigSchema({ keys: ["session, auth_email"] }); + expect(requestedUrl).toContain("/config/schema"); expect(requestedUrl).toContain("keys=session"); expect(requestedUrl).toContain("keys=auth_email"); + expect(requestedUrl).not.toContain("keys=+"); + expect(requestedUrl).not.toContain("keys=%20"); }); test("errors when production instance not configured", async () => { diff --git a/packages/cli-core/src/lib/plapi.ts b/packages/cli-core/src/lib/plapi.ts index a4b50830..caa5a743 100644 --- a/packages/cli-core/src/lib/plapi.ts +++ b/packages/cli-core/src/lib/plapi.ts @@ -85,7 +85,8 @@ function appendKeys(url: URL, keys?: string[]): void { if (!keys?.length) return; for (const key of keys) { for (const k of key.split(",")) { - if (k) url.searchParams.append("keys", k); + const trimmed = k.trim(); + if (trimmed) url.searchParams.append("keys", trimmed); } } }