Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c6123ea
fix(e2e): bound network and setup steps to kill flaky 300s timeouts
rafa-thayto Jun 22, 2026
5ce158a
fix(e2e): kill stalled setup subprocesses; harden timeout test
rafa-thayto Jun 22, 2026
7d8c1a9
fix(e2e): revert setup steps to Bun.$ + Promise.race timeout
rafa-thayto Jun 22, 2026
e3bcc00
fix(e2e): drop per-step setup timeouts (revert Bun.spawn deadlock)
rafa-thayto Jun 22, 2026
5cd1636
fix(e2e): bound npm ci fetch timeout to stop 300s setup hangs
rafa-thayto Jun 22, 2026
beac043
fix(e2e): cap npm fetch-timeout so a stalled install can't hang setup
rafa-thayto Jun 22, 2026
a2fca89
fix(e2e): serialize fixtures to stop Bun.$ subprocess stalls under load
rafa-thayto Jun 22, 2026
cef0d3c
fix(e2e): retry clerk link/init on non-fetch stalls
rafa-thayto Jun 22, 2026
aa14b3e
fix(e2e): wrap all setup steps in retry; restore parallel run
rafa-thayto Jun 23, 2026
d0bf0eb
fix(e2e): link in agent mode so the retry is idempotent
rafa-thayto Jun 23, 2026
f9f6e49
feat(webhooks): add webhook error codes
rafa-thayto Jun 9, 2026
3604b45
feat(webhooks): persist per-instance relay state in CLI config
rafa-thayto Jun 9, 2026
b46e411
feat(webhooks): add typed PLAPI client functions for the 13 webhook r…
rafa-thayto Jun 9, 2026
84bb0c3
feat(webhooks): register webhooks command group with auth preAction gate
rafa-thayto Jun 9, 2026
88e0c34
feat(webhooks): add 'webhooks list' command
rafa-thayto Jun 9, 2026
9997589
feat(webhooks): add 'webhooks get' command
rafa-thayto Jun 9, 2026
f21be76
feat(webhooks): add 'webhooks event-types' command
rafa-thayto Jun 9, 2026
8a07aff
feat(webhooks): add 'webhooks secret' command with --rotate
rafa-thayto Jun 9, 2026
b80a5ec
feat(webhooks): add 'webhooks delete' command
rafa-thayto Jun 9, 2026
af44ad6
feat(webhooks): add 'webhooks update' command
rafa-thayto Jun 9, 2026
72e3ebb
feat(webhooks): add 'webhooks create' command
rafa-thayto Jun 9, 2026
f43e802
feat(webhooks): add 'webhooks messages' command
rafa-thayto Jun 9, 2026
03c54ef
feat(webhooks): add 'webhooks replay' command
rafa-thayto Jun 9, 2026
c77b8dc
feat(webhooks): add 'webhooks trigger' command
rafa-thayto Jun 9, 2026
4804c62
feat(webhooks): add 'webhooks open' command
rafa-thayto Jun 9, 2026
22d45c6
feat(webhooks): add offline 'webhooks verify' command
rafa-thayto Jun 9, 2026
04cd67f
feat(webhooks): add pure relay protocol helpers
rafa-thayto Jun 9, 2026
68f9fce
feat(webhooks): add relay client, forwarder, and listen rendering
rafa-thayto Jun 9, 2026
ccbd2aa
feat(webhooks): add 'webhooks listen' command
rafa-thayto Jun 9, 2026
fc7b2f8
docs(webhooks): sync root README help output and add agent-mode outpu…
rafa-thayto Jun 9, 2026
cf9a77d
docs(changeset): add the clerk webhooks command group
rafa-thayto Jun 9, 2026
94caee8
style(webhooks): resolve all oxlint warnings in the webhooks group
rafa-thayto Jun 9, 2026
f7fdc6b
fix(webhooks): validate trigger event type before endpoint resolution…
rafa-thayto Jun 9, 2026
049ce62
feat(webhooks): structured agent-mode output for 'webhooks verify' (s…
rafa-thayto Jun 9, 2026
4beca20
fix(webhooks): fail fast on confirmation gates and unblock 'verify' s…
rafa-thayto Jun 9, 2026
b4d8720
test(webhooks): spell the no---json agent case as an empty flags object
rafa-thayto Jun 9, 2026
9df21a6
chore(webhooks): remove stray test fixture committed under packages/c…
rafa-thayto Jun 9, 2026
f66e332
fix(webhooks): relay token carries the c_ prefix on the wire and in t…
rafa-thayto Jun 10, 2026
379e1f6
fix(webhooks): add missing imports and clean up rebase artifacts in c…
rafa-thayto Jun 15, 2026
9889ce1
test(webhooks): cover relay client and harden splitCommaList empty ha…
rafa-thayto Jun 18, 2026
a1352d3
fix(webhooks): harden verify, create, replay, and relay edge cases
rafa-thayto Jun 24, 2026
dba0f5b
feat(webhooks): add --relay-only/--token and register via the registr…
rafa-thayto Jun 25, 2026
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
5 changes: 5 additions & 0 deletions .changeset/fix-network-request-timeout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Add a default 60s timeout to all outbound CLI network requests. Previously a stalled connection to a Clerk API could hang a command indefinitely (with no error and no way to recover other than Ctrl-C); requests now abort with a clear, tagged error after 60s. A caller-supplied `AbortSignal` still composes with this default, so tighter per-call budgets continue to win.
7 changes: 7 additions & 0 deletions .changeset/webhooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"clerk": minor
---

Add the `clerk webhooks` command group for managing webhook endpoints and deliveries from the terminal: `list`, `get`, `create`, `update`, `delete`, `secret [--rotate]`, `event-types`, `messages`, `replay`, `listen`, `trigger`, `verify`, and `open`.

`webhooks listen` supports `--relay-only` to run the local relay tunnel with no Clerk backend (no PLAPI, no auth), and `--token <c_…>` to pin a stable, shareable relay URL. The relay token is persisted per instance, so the relay URL stays the same across restarts.
68 changes: 68 additions & 0 deletions .claude/rules/command-registration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
description: Command registration conventions — every command group registers via register<Name>(program) from its index.ts, listed in the registrants array
paths:
- "packages/cli-core/src/cli-program.ts"
- "packages/cli-core/src/commands/*/index.ts"
alwaysApply: false
---

Every command group is wired into the root program through a **registrant function**, never inline in `createProgram()`.

## The pattern

1. Each command group exports `register<Name>(program: Program): void` from `packages/cli-core/src/commands/<name>/index.ts`. It builds the whole `program.command("<name>")` subtree (options, arguments, `.setExamples()`, subcommands) and wires each `.action()` to the handler functions in sibling files.
2. `cli-program.ts` imports that function and adds it to the `registrants: CommandRegistrant[]` array. `createProgram()` only configures the root program + global hooks, then loops `for (const register of registrants) register(program)`.

**Do not** build a command tree inline inside `createProgram()`. If you're adding a `program.command(...)` (or `webhooks`-style group) directly in `cli-program.ts`, stop — move it to a `register<Name>` in the group's `index.ts` and append the function to `registrants` instead.

## `index.ts` shape

```ts
import type { Program } from "../../cli-program.ts";
import { list } from "./list.ts";
import { create } from "./create.ts";

export function registerApps(program: Program): void {
const apps = program.command("apps").description("Manage your Clerk applications");

apps
.command("list")
.description("List your Clerk applications")
.option("--json", "Output as JSON")
.setExamples([{ command: "clerk apps list", description: "List all applications" }])
.action(list);

apps.command("create").argument("<name>", "Application name").action(create);
}
```

- Import the `Program` type from `../../cli-program.ts` (type-only — no runtime cycle, this is the established pattern).
- Keep handler _logic_ in sibling files (`list.ts`, `create.ts`, …); `index.ts` is wiring only. A handler-map object (e.g. `const handlers = { list, create }`) is fine when actions need typed `Parameters<typeof handlers.x>[0]` casts.

## `cli-program.ts` shape

```ts
import { registerApps } from "./commands/apps/index.ts";
// …
const registrants: CommandRegistrant[] = [
registerInit,
registerApps,
// … one entry per command group, in display order …
registerExtras,
];

export function createProgram(): Program {
const program = new Command() /* … global options … */ as Program;
program.hook("preAction" /* … */);
for (const register of registrants) register(program);
return program;
}
```

Helpers used by only one group (e.g. `createOption`, `parseIntegerOption`, `getAuthToken`) belong in that group's `index.ts`, not imported into `cli-program.ts`.

## Groups with global options, a group-level hook, and subcommands

Build the group exactly as above and attach its concerns inside the same `register<Name>`: parent `.option(...)` flags inherited via `optsWithGlobals()`, a group `webhooks.hook("preAction", …)` for shared gating (e.g. auth), and one `.command(...)` per subcommand. See `commands/webhooks/index.ts` (`registerWebhooks`) for a full multi-subcommand group with inherited `--app`/`--instance`/`--json` options and an auth `preAction`.

Related: [commands.md](./commands.md) (per-command directory + README + agent-mode rules) and [completion.md](./completion.md) (keep `.choices()` / `__complete.ts` in sync when adding commands or options).
1 change: 1 addition & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"files": [
"packages/cli-core/src/cli.ts",
"packages/cli-core/src/cli-program.ts",
"packages/cli-core/src/lib/signals.ts",
"scripts/*"
],
"rules": {
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Commands:
open Open Clerk resources in your browser
apps Manage your Clerk applications
users [options] Manage Clerk users
webhooks [options] Manage webhook endpoints and deliveries
env Manage environment variables
config Manage instance configuration
enable Enable Clerk features on the linked instance
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "bun run --filter @clerk/cli-core build",
"dev": "bun run --cwd packages/cli-core dev",
"test": "bun test 'packages/cli-core/src/' 'packages/extras/src/' 'scripts/' --parallel --only-failures",
"test:e2e": "bun test 'test/e2e/' --retry 1 --parallel --only-failures",
"test:e2e": "bun test 'test/e2e/' --retry 1 --parallel=4 --only-failures",
"test:e2e:op": "bun run scripts/run-e2e-op.ts",
"e2e:refresh-fixtures": "bun run scripts/refresh-e2e-fixtures.ts",
"typecheck": "bun run --filter './packages/*' typecheck && tsc --noEmit -p scripts/tsconfig.json && tsc --noEmit -p test/e2e/tsconfig.json",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli-core/src/cli-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { clerkHelpConfig } from "./lib/help.ts";
import { isAgent } from "./mode.ts";
import { log } from "./lib/log.ts";
import { maybeNotifyUpdate, getCurrentVersion } from "./lib/update-check.ts";
import { registerWebhooks } from "./commands/webhooks/index.ts";
import { registerExtras } from "@clerk/cli-extras";

/**
Expand Down Expand Up @@ -69,6 +70,7 @@ const registrants: CommandRegistrant[] = [
registerCompletion,
registerUpdate,
registerDeploy,
registerWebhooks,
registerExtras,
];

Expand Down
4 changes: 2 additions & 2 deletions packages/cli-core/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bun
import { createProgram, runProgram } from "./cli-program.ts";
import { EXIT_CODE } from "./lib/errors.ts";
process.on("SIGINT", () => process.exit(EXIT_CODE.SIGINT));
import { cliSigintHandler } from "./lib/signals.ts";
process.on("SIGINT", cliSigintHandler);

// Fast path for shell completion — intercept before Commander parses
// to avoid validation errors on partial input from Tab presses.
Expand Down
Loading