feat(*): ship Clerk AGENTS.md rules for AI coding agents by royanger · Pull Request #10320 · clerk/javascript · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0132694
feat(shared): add AGENTS.md writer for Clerk agent rules
royanger Oct 19, 2026
18c4ee4
feat(repo): ship AGENTS.md with each Clerk SDK
royanger Oct 19, 2026
f5a3211
feat(*): write Clerk agent rules to AGENTS.md in development
royanger Oct 19, 2026
f8b581f
feat(react,vue): add Vite plugins that write Clerk agent rules
royanger Oct 19, 2026
853d333
feat(upgrade): add --agents-md flag
royanger Oct 19, 2026
5dc481b
refactor(shared): recommend the Clerk plugin in AGENTS.md
royanger Oct 20, 2026
0e4331e
fix(shared): import AGENTS.md from an existing CLAUDE.md
royanger Oct 20, 2026
b01bbba
refactor(shared): shorten the AGENTS.md intro and link the Markdown docs
royanger Oct 20, 2026
d667c3a
fix(*): address review feedback on Clerk agent rules
royanger Oct 20, 2026
3bacc8e
feat(*): write Clerk agent rules on every dev run, not only under an …
royanger Oct 21, 2026
4117b1d
refactor(*): drop isAgentEnvironment and test the Astro and Nuxt agen…
royanger Oct 21, 2026
7127b70
feat(*): write Clerk agent rules from server middleware, Electron, an…
royanger Oct 21, 2026
627310a
feat(chrome-extension): add Vite plugin that writes Clerk agent rules
royanger Oct 21, 2026
1ae1fb5
fix(shared): write Clerk agent rules to the nearest package.json folder
royanger Oct 21, 2026
f486831
feat(*): log when Clerk writes agent rules in development
royanger Oct 21, 2026
0de4817
Merge branch 'main' into ra/write-agents.md-file-with-clerk-instructions
royanger Oct 22, 2026
297da31
Merge branch 'main' into ra/write-agents.md-file-with-clerk-instructions
royanger Oct 22, 2026
c67d819
test(repo): update typedoc snapshot for InstanceType union order
royanger Oct 22, 2026
3898e19
Merge branch 'main' into ra/write-agents.md-file-with-clerk-instructions
royanger Oct 22, 2026
a40ba98
fix(shared): keep SDK agent rules blocks in sorted order
royanger Oct 22, 2026
494c961
Merge branch 'main' into ra/write-agents.md-file-with-clerk-instructions
royanger Oct 23, 2026
1ee6e79
Merge branch 'main' into ra/write-agents.md-file-with-clerk-instructions
royanger Oct 23, 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
14 changes: 14 additions & 0 deletions .changeset/chrome-extension-agent-rules-vite.md
15 changes: 15 additions & 0 deletions .changeset/react-vue-agent-rules-vite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@clerk/react': minor
'@clerk/vue': minor
---

Add Vite plugins that write a Clerk section to the project's `AGENTS.md` when the Vite dev server starts. Nothing is written during builds, tests, or CI, and setting `CLERK_AGENTS_MD=false` turns it off.

```ts
import { clerkReactAgentRules } from '@clerk/react/vite';
// or: import { clerkVueAgentRules } from '@clerk/vue/vite';

export default defineConfig({
plugins: [react(), clerkReactAgentRules()],
});
```
5 changes: 5 additions & 0 deletions .changeset/shared-agent-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/shared': minor
---

Add `@clerk/shared/agentRules`, `@clerk/shared/agentRulesWriter`, and `@clerk/shared/vite` for writing Clerk sections to a project's `AGENTS.md`. Add `isClerkAgentRulesWriteAllowed()` to `@clerk/shared/utils`, which returns `false` in production, in tests, in CI, or when `CLERK_AGENTS_MD=false`.
19 changes: 19 additions & 0 deletions .changeset/ship-sdk-agents-md.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@clerk/astro': patch
'@clerk/backend': patch
'@clerk/chrome-extension': patch
'@clerk/clerk-js': patch
'@clerk/electron': patch
'@clerk/expo': patch
'@clerk/express': patch
'@clerk/fastify': patch
'@clerk/hono': patch
'@clerk/nextjs': patch
'@clerk/nuxt': patch
'@clerk/react': patch
'@clerk/react-router': patch
'@clerk/tanstack-react-start': patch
'@clerk/vue': patch
---

Ship an `AGENTS.md` file in the package. It lists the current APIs for this version and recommends the Clerk plugin, so AI coding agents can read it from `node_modules` instead of relying on outdated training data.
5 changes: 5 additions & 0 deletions .changeset/upgrade-agents-md.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/upgrade': minor
---

Add an `--agents-md` flag that writes Clerk sections to the project's `AGENTS.md` for each installed Clerk SDK, then exits. Interactive upgrades also offer to write these sections.
14 changes: 14 additions & 0 deletions .changeset/write-agents-md-in-dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@clerk/astro': minor
'@clerk/electron': minor
'@clerk/expo': minor
'@clerk/express': minor
'@clerk/fastify': minor
'@clerk/hono': minor
'@clerk/nextjs': minor
'@clerk/nuxt': minor
'@clerk/react-router': minor
'@clerk/tanstack-react-start': minor
---

When your app runs in development, Clerk now adds a Clerk section to the project's root `AGENTS.md`, creating the file if needed. It also creates a `CLAUDE.md` that imports `AGENTS.md`, or adds an `@AGENTS.md` import to an existing `CLAUDE.md` that doesn't load it. The section tells the agent to read `node_modules/@clerk/<sdk>/AGENTS.md` before writing auth code and recommends the Clerk plugin (`npx plugins add clerk/skills`). Nothing is written in production, tests, or CI, and setting `CLERK_AGENTS_MD=false` turns it off. The section is written by the Astro and Nuxt dev servers, the Expo config plugin, the first `auth()` call or `clerkMiddleware()` request in Next.js, TanStack React Start's `clerkMiddleware()`, `clerkMiddleware()` in Express, Hono, and React Router, Fastify's `clerkPlugin`, and `createClerkBridge()` in an unpackaged Electron app.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ jobs:
- name: Check Dashboard links
run: pnpm lint:dashboard-links

- name: Check SDK AGENTS.md
run: pnpm lint:agents-md

- name: Verify localizations are generated
run: |
pnpm --filter @clerk/localizations generate
Expand Down
2 changes: 1 addition & 1 deletion .typedoc/__tests__/__snapshots__/clerk-properties.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
| <a id="billing"></a> `billing` | [`BillingNamespace`](/docs/reference/objects/billing) | The `Billing` object used for managing billing. |
| <a id="client"></a> `client` | <code>undefined \| [ClientResource](/docs/reference/objects/client)</code> | The `Client` object for the current window. |
| <a id="domain"></a> `domain` | `string` | The current Clerk app's domain. Prefixed with `clerk.` on production if not already prefixed. Returns `""` when ran on the server. |
| <a id="instancetype"></a> `instanceType` | <code>undefined \| "production" \| "development"</code> | Indicates whether the Clerk instance is running in a production or development environment. |
| <a id="instancetype"></a> `instanceType` | <code>undefined \| "development" \| "production"</code> | Indicates whether the Clerk instance is running in a production or development environment. |
| <a id="issatellite"></a> `isSatellite` | `boolean` | Indicates whether the instance is a satellite app. |
| <a id="issignedin"></a> `isSignedIn` | `boolean` | Indicates whether the current user has a valid signed-in client session. |
| <a id="isstandardbrowser"></a> `isStandardBrowser` | <code>undefined \| boolean</code> | Indicates whether the instance is being loaded in a standard browser environment. Set to `false` on native platforms where cookies cannot be set. When `undefined`, Clerk assumes a standard browser. |
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"license": "MIT",
"scripts": {
"agents-md:sync": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON ./scripts/sync-agents-md.mjs",
"build": "FORCE_COLOR=1 turbo build --concurrency=${TURBO_CONCURRENCY:-80%}",
"build:declarations": "FORCE_COLOR=1 turbo build:declarations --concurrency=${TURBO_CONCURRENCY:-80%} --filter=@clerk/nextjs",
"bundlewatch": "turbo run bundlewatch",
Expand All @@ -20,6 +21,7 @@
"preinstall": "npx only-allow pnpm",
"integration:secrets": "node ./scripts/1password-keys.mjs",
"lint": "node ./scripts/lint.mjs",
"lint:agents-md": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON ./scripts/sync-agents-md.mjs --check",
"lint:attw": "FORCE_COLOR=1 turbo lint:attw",
"lint:dashboard-links": "node ./scripts/check-dashboard-links.mjs",
"lint:fix": "FORCE_COLOR=1 turbo lint -- --fix",
Expand Down
34 changes: 34 additions & 0 deletions packages/astro/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Clerk + Astro

Read the installed `@clerk/astro` types before changing auth code. If a symbol is not in those types, it does not exist.

### Current APIs

- Register the default export of `@clerk/astro` in `integrations` in `astro.config`. `server` output needs an adapter.
- Use `clerkMiddleware()` from `@clerk/astro/server` in `src/middleware.ts`.
- `createRouteMatcher()` was removed. Check auth in each page or API route with `Astro.locals.auth()` or `locals.auth()`, then call `redirectToSignIn()` or return a 401.
- `locals.currentUser()` returns the Backend `User` or `null`.
- `<SignedIn>`, `<SignedOut>`, and `<Protect>` were removed. Import `<Show>` from `@clerk/astro/components` (or `@clerk/astro/react`). Its `when` prop is required, for example `<Show when="signed-in">`.
- The `as` prop was removed from `<SignInButton>`, `<SignUpButton>`, and `<SignOutButton>`. Use `asChild` with a child element.
- Clerk Elements is removed. Never use it.

<!-- BEGIN:clerk-plugin -->

### Clerk plugin

The Clerk plugin gives agents Clerk's skills and MCP server. If it is not installed, tell the user they can install it. Do not run the command yourself:

```sh
npx plugins add clerk/skills
```

Install steps for each tool: https://clerk.com/docs/guides/ai/overview
Source: https://github.com/clerk/skills

Ask the user before calling any Clerk tool that creates or changes something in their Clerk account.

### Docs

Start at https://clerk.com/docs/llms.txt. Add `.md` to any docs URL to get Markdown.

<!-- END:clerk-plugin -->
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"AGENTS.md",
"dist",
"client",
"server",
Expand Down
71 changes: 71 additions & 0 deletions packages/astro/src/integration/__tests__/agent-rules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type * as AgentRules from '@clerk/shared/agentRules';
import { writeClerkAgentRules } from '@clerk/shared/agentRules';
import { automatedEnvironmentVariables } from '@clerk/shared/utils';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

vi.mock('@clerk/shared/agentRules', async importOriginal => ({
...(await importOriginal<typeof AgentRules>()),
writeClerkAgentRules: vi.fn(),
}));

const loadWriteAgentRulesOnce = async () => (await import('../agent-rules')).writeAgentRulesOnce;

describe('writeAgentRulesOnce', () => {
beforeEach(() => {
vi.resetModules();
vi.mocked(writeClerkAgentRules).mockReset();
for (const name of automatedEnvironmentVariables) {
vi.stubEnv(name, '');
}
vi.stubEnv('CLERK_AGENTS_MD', '');
vi.stubEnv('NODE_ENV', 'development');
});

afterEach(() => {
vi.unstubAllEnvs();
});

it('writes the Astro rules once per process', async () => {
const writeAgentRulesOnce = await loadWriteAgentRulesOnce();

writeAgentRulesOnce(new URL('file:///app/'));
writeAgentRulesOnce(new URL('file:///app/'));

expect(writeClerkAgentRules).toHaveBeenCalledTimes(1);
expect(writeClerkAgentRules).toHaveBeenCalledWith(expect.objectContaining({ cwd: '/app/', sdks: ['astro'] }));
});

it('skips in CI', async () => {
vi.stubEnv('CI', 'true');

(await loadWriteAgentRulesOnce())(new URL('file:///app/'));

expect(writeClerkAgentRules).not.toHaveBeenCalled();
});

it('skips in production', async () => {
vi.stubEnv('NODE_ENV', 'production');

(await loadWriteAgentRulesOnce())(new URL('file:///app/'));

expect(writeClerkAgentRules).not.toHaveBeenCalled();
});

it('skips when CLERK_AGENTS_MD is false', async () => {
vi.stubEnv('CLERK_AGENTS_MD', 'false');

(await loadWriteAgentRulesOnce())(new URL('file:///app/'));

expect(writeClerkAgentRules).not.toHaveBeenCalled();
});

it('does not throw when writing fails', async () => {
vi.mocked(writeClerkAgentRules).mockImplementation(() => {
throw new Error('EACCES');
});

const writeAgentRulesOnce = await loadWriteAgentRulesOnce();

expect(() => writeAgentRulesOnce(new URL('file:///app/'))).not.toThrow();
});
});
22 changes: 22 additions & 0 deletions packages/astro/src/integration/agent-rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';

import { logClerkAgentRulesResult, writeClerkAgentRules } from '@clerk/shared/agentRules';
import { isClerkAgentRulesWriteAllowed } from '@clerk/shared/utils';

let didWrite = false;

export function writeAgentRulesOnce(root: URL) {
if (didWrite || !isClerkAgentRulesWriteAllowed()) {
return;
}

didWrite = true;

try {
logClerkAgentRulesResult(writeClerkAgentRules({ cwd: fileURLToPath(root), fs, path, sdks: ['astro'] }));
} catch {
// Writing agent hints must never break a dev server.
}
}
5 changes: 5 additions & 0 deletions packages/astro/src/integration/create-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { envField } from 'astro/config';

import { name as packageName, version as packageVersion } from '../../package.json';
import type { AstroClerkIntegrationParams } from '../types';
import { writeAgentRulesOnce } from './agent-rules';
import { buildBeforeHydrationSnippet, buildPageLoadSnippet } from './snippets';
import { vitePluginAstroConfig } from './vite-plugin-astro-config';

Expand Down Expand Up @@ -31,6 +32,10 @@ function createIntegration<Params extends HotloadAstroClerkIntegrationParams>()
name: '@clerk/astro/integration',
hooks: {
'astro:config:setup': ({ config, injectScript, updateConfig, logger, command }) => {
if (command === 'dev') {
writeAgentRulesOnce(config.root);
}

if (['server', 'hybrid'].includes(config.output) && !config.adapter) {
logger.error('Missing adapter, please update your Astro config to use one.');
}
Expand Down
32 changes: 32 additions & 0 deletions packages/backend/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## Clerk + Backend

Read the installed `@clerk/backend` types before changing auth code. If a symbol is not in those types, it does not exist.

### Current APIs

- Create the client with `createClerkClient({ secretKey, publishableKey })` from `@clerk/backend`.
- Authenticate a Fetch `Request` with `await clerkClient.authenticateRequest(request)`. Check `requestState.isAuthenticated`, not the deprecated `isSignedIn`, then read auth from `requestState.toAuth()`.
- Verify machine tokens with `verify()`: `clerkClient.apiKeys.verify(secret)`, `clerkClient.m2m.verify(params)`, `clerkClient.idPOAuthAccessToken.verify(token)`. `verifySecret()`, `verifyAccessToken()`, and `m2m.verifyToken()` are removed. The top-level `verifyToken()` export still exists.
- Verify webhooks with `verifyWebhook(request)` from `@clerk/backend/webhooks`. It reads `CLERK_WEBHOOK_SIGNING_SECRET` by default.
- Clerk Elements is removed. Never use it.

<!-- BEGIN:clerk-plugin -->

### Clerk plugin

The Clerk plugin gives agents Clerk's skills and MCP server. If it is not installed, tell the user they can install it. Do not run the command yourself:

```sh
npx plugins add clerk/skills
```

Install steps for each tool: https://clerk.com/docs/guides/ai/overview
Source: https://github.com/clerk/skills

Ask the user before calling any Clerk tool that creates or changes something in their Clerk account.

### Docs

Start at https://clerk.com/docs/llms.txt. Add `.md` to any docs URL to get Markdown.

<!-- END:clerk-plugin -->
1 change: 1 addition & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
},
"main": "./dist/index.js",
"files": [
"AGENTS.md",
"dist",
"errors",
"internal",
Expand Down
33 changes: 33 additions & 0 deletions packages/chrome-extension/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Clerk + Chrome Extension

Read the installed `@clerk/chrome-extension` types before changing auth code. If a symbol is not in those types, it does not exist.

### Current APIs

- Pass `publishableKey` to `<ClerkProvider>`. There is no env var fallback. Pass `syncHost` to sync sessions with your web app.
- In the background service worker, use `createClerkClient()` from `@clerk/chrome-extension/background`. `__unstable__createClerkClient` was renamed.
- `<SignedIn>`, `<SignedOut>`, and `<Protect>` were removed. Use `<Show when="signed-in">` or `<Show when="signed-out">`.
- `<GoogleOneTap>` renders nothing in extensions.
- The root `useSignIn()` and `useSignUp()` are the Core 3 hooks. The old `isLoaded`-style hooks are in `@clerk/chrome-extension/legacy`.
- Clerk Elements is removed. Never use it.

<!-- BEGIN:clerk-plugin -->

### Clerk plugin

The Clerk plugin gives agents Clerk's skills and MCP server. If it is not installed, tell the user they can install it. Do not run the command yourself:

```sh
npx plugins add clerk/skills
```

Install steps for each tool: https://clerk.com/docs/guides/ai/overview
Source: https://github.com/clerk/skills

Ask the user before calling any Clerk tool that creates or changes something in their Clerk account.

### Docs

Start at https://clerk.com/docs/llms.txt. Add `.md` to any docs URL to get Markdown.

<!-- END:clerk-plugin -->
14 changes: 13 additions & 1 deletion packages/chrome-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,31 @@
"./types": {
"types": "./dist/types/types/index.d.ts"
},
"./vite": {
"import": {
"types": "./dist/types/vite.d.ts",
"default": "./dist/esm/vite.js"
},
"require": {
"types": "./dist/types/vite.d.ts",
"default": "./dist/cjs/vite.js"
}
},
"./package.json": "./package.json"
},
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"files": [
"AGENTS.md",
"background",
"client",
"dist",
"internal",
"legacy",
"react",
"types"
"types",
"vite"
],
"scripts": {
"build": "tsup",
Expand Down
3 changes: 3 additions & 0 deletions packages/chrome-extension/src/vite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { clerkAgentRulesVitePlugin } from '@clerk/shared/vite';

export const clerkChromeExtensionAgentRules = () => clerkAgentRulesVitePlugin('chrome-extension');
1 change: 1 addition & 0 deletions packages/chrome-extension/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default defineConfig(overrideOptions => {
'./src/legacy.ts',
'./src/react/index.ts',
'./src/types/index.ts',
'./src/vite.ts',
],
bundle: true,
clean: true,
Expand Down
5 changes: 5 additions & 0 deletions packages/chrome-extension/vite/package.json
Loading
Loading