This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is the TypeScript reference implementation of AT Protocol, the decentralized social media protocol behind Bluesky. It is a pnpm monorepo (runtime floor Node.js ≥22; local dev and CI build/verify default to Node 24 via .nvmrc — only the test matrix runs on 22) containing client libraries, schema/codegen tooling, and the two main service implementations: the Personal Data Server (PDS) and the app.bsky AppView.
Workspace layout (see pnpm-workspace.yaml and tsconfig.json):
- packages/* — top-level libraries:
api,common,crypto,identity,lexicon,repo,syntax,xrpc,xrpc-server,pds,bsky,bsync,ozone,dev-env,dev-infra, etc. - packages/lex/* — the modern type-safe Lexicon SDK family (
@atproto/lex,lex-builder,lex-cbor,lex-client,lex-data,lex-document,lex-json,lex-resolver,lex-server,lex-schema,lex-installer,lex-password-session). New service code should use this in preference to the older@atproto/api/@atproto/lexicon/@atproto/xrpc/@atproto/lex-clistack — see thelex-sdkskill. - packages/oauth/* — OAuth client/provider implementations and JWK helpers.
- packages/internal/* —
@atproto-labs/*internal shared utilities (fetch, handle/identity/DID resolvers, simple-store, pipe, xrpc-utils, rollup plugin). - services/{pds,bsky,bsync,ozone} — thin runtime wrappers; the actual implementation code lives in
packages/{pds,bsky,bsync,ozone}. - lexicons/ — canonical JSON Lexicon schemas for
com.atproto.*,app.bsky.*,chat.bsky.*,tools.ozone.*. These are the source-of-truth that codegen consumes. - interop-test-files/ — language-neutral protocol conformance fixtures. Don't edit unless changing protocol-level behavior; these are shared across SDKs.
Whole-repo verification commands (from root):
pnpm verify # parallel: style + lint
pnpm build --force # single root `tsgo --build` over the project-references graph, then UI bundlers
pnpm codegen # parallel codegen across all packages; runs from .ts sources via Node type-strippingThe pipeline is: build:tooling → codegen → prebuild → build (one tsgo --build tsconfig.json at the root) → postbuild (Vite/UI bundlers).
The Makefile wraps the most common entry points: make build, make test, make lint, make fmt, make codegen, make run-dev-env (boots the in-process PDS+AppView+bsync+plc+ozone constellation), make run-dev-env-logged (same with pino-pretty log output), make fmt-lexicons (eslint-fix on lexicons/*.json).
Per-package work — always run from inside the package directory, not from the root:
cd packages/<pkg>
pnpm exec tsgo --build tsconfig.build.json # build that package + its referenced deps
pnpm test # run that package's test suite
pnpm exec prettier --write <path> # format specific files only
pnpm exec eslint --fix <path> # lint specific files onlyEvery package now ships a tsconfig.build.json (composite, with explicit references to its workspace deps) and a tsconfig.test.json for the test sources. The root tsconfig.json is a project-graph aggregator only.
Avoid pnpm run style:fix (whole-repo prettier) unless the user explicitly asks for a repo-wide formatting pass.
Before writing or extending any test, invoke the testing skill (.claude/skills/testing/SKILL.md). It covers runner selection (vitest vs jest), file layout, and tsconfig setup. For browser-driven UI tests, or for demoing/debugging the OAuth flows or the Account Manager interface, invoke the playwright skill (.claude/skills/playwright/SKILL.md) instead.
After editing anything under lexicons/, run pnpm codegen from the repo root before building or testing. The bsky package additionally runs buf generate for protobuf bindings against bsync as part of its codegen step.
For everything else lexicon-related — lex install / lex build, the per-package prebuild setup, and migrating from the legacy @atproto/api / @atproto/lexicon / @atproto/xrpc / @atproto/lex-cli stack to the new @atproto/lex family — invoke the lex-sdk skill (.claude/skills/lex-sdk/SKILL.md).
- Lexicons are the contract. The JSON files in lexicons/ drive both client types and server route validation. Service packages don't hand-write XRPC method signatures — they import the generated definitions from their
src/lexicons/directory (gitignored / regenerated). - PDS (packages/pds) — a single-tenant atproto server: account management, repo storage (kysely-over-sqlite), actor storage (kysely-over-postgres), email, OAuth provider, blob storage. Runtime entry point is services/pds; production code is in
packages/pds/src. - Bsky AppView (packages/bsky) — read-side service for
app.bsky.*queries (timelines, profiles, feed generators, hydration pipeline, GraphQL-like view composition). Talks to PDSes via XRPC and tobsyncvia Connect-RPC (protobuf inpackages/bsky/proto). Runtime entry point in services/bsky. - Bsync (packages/bsync) — internal service for cross-AppView synchronization (mutes, notifications). Connect-RPC interface.
- Ozone (packages/ozone) — moderation service for
tools.ozone.*. - dev-env (packages/dev-env) — boots a full PDS + AppView + bsync + plc + ozone constellation in-process for tests and the
make run-dev-envREPL. Most integration tests inpds/bsky/ozoneuse it as a fixture builder. - OAuth (packages/oauth) — split into
oauth-types(shared schemas),oauth-client(browser/node/expo variants),oauth-provider(used inside the PDS), andoauth-scopes.
- Node ≥22 runtime floor; build/dev default to Node 24 (
.nvmrc). ESM only ("type": "module"in every package). Usenode --enable-source-mapsfor production-style runs. - TypeScript compilation uses
tsgo(TS7,@typescript/native-preview), nottsc. There is no per-packagetypescriptdevDependency —tsgois hoisted at the root. - Import paths use workspace protocol (
workspace:^). Don't pin internal packages to a published version. - Don't refactor unrelated code; this project's contribution guidelines explicitly discourage large refactors and unsolicited tooling changes (see README.md "Contributions").
- Don't add new dependencies without strong justification.
- When picking lexicon SDK APIs in new code, prefer the
@atproto/lexfamily over the old@atproto/apistack.
- Stale codegen. If the build fails due to a generated file in packages/api or packages/ozone being out of date, run
pnpm run codegen && pnpm run buildfrom those packages, then re-run the build. This is only needed on these two packages because theirprebuildstep skips codegen as a performance optimization. - Codegen ran but produced stale output. Codegen relies on
pnpm build:toolingto build the@atproto/lex-cliand@atproto/lex-builderpackages first. If you see a codegen failure, runpnpm build:toolingfrom the root, then re-run codegen. - End-to-end test fails with stale infra. If docker containers persist across test runs, reset them with
cd packages/dev-infra && docker compose down --volumes.