encore-code-review — Claude Code skill: Encore.ts review
A skill that reviews Encore.ts code before merge: infrastructure inside functions, require() instead of imports, raw Error instead of APIError.
npx skills add encoredev/skills --skill encore-code-review -a claude-code
npx skills add encoredev/skills --skill code-review -a claude-codeWhat encore-code-review does
The skill kicks in when you review a pull request or audit existing Encore.ts code. It walks a checklist and tags findings as [CRITICAL], [WARNING] or [GOOD], each with a file and line. Greenfield code still being written is out of scope — there is nothing to catch yet.
What it looks for: SQLDatabase and Topic declared inside functions instead of at package level, direct imports from another service instead of ~encore/clients, require() where ES6 imports belong, returning null instead of throw APIError.notFound(...), and string concatenation in SQL instead of template literals. It also flags non-idempotent subscription handlers: with at-least-once delivery a retry can charge a customer twice, so the skill suggests checking status before processing. Then it checks whether secret() is called at module level during import, and whether an internal endpoint is left with expose: true.
The skill assumes a specific stack: Encore.ts with encore.dev/api, encore.dev/pubsub and ~encore/clients. Point it at a different framework and the checks won't line up with your code. Install runs through npx skills add or gh skill install, dropping the skill file into your platform's skills directory.
What it helps with
- catch a database or topic declared inside a function before merge
- swap a direct cross-service import for
~encore/clients - find
nullreturns that should beAPIError - check subscription handlers for idempotency
- strip string concatenation out of SQL queries
Example prompts for the agent
- review this pull request for Encore.ts anti-patterns
- audit the user service before merge
- run the review checklist over the files in
services/order
How to install encore-code-review
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill encore-code-review -a claude-codenpx skills add encoredev/skills --skill code-review -a claude-code2Manually: put the files in place
~/.claude/skills/code-review/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/code-review/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill code-review -a codex2Manually: put the files in place
.agents/skills/code-review/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add encoredev/skills --skill code-review -a cursor2Manually: put the files in place
.cursor/skills/code-review/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install encoredev/skills code-review2Manually: put the files in place
.github/skills/code-review/SKILL.md1Skills CLI (Vercel)
npx skills add encoredev/skills --skill code-review -a gemini-cli2Platform CLI
gemini skills install https://github.com/encoredev/skills --path encore/code-reviewManually: put the files in place
.devin/skills/code-review/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add encoredev/skills --skill code-review -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about encore-code-review
How do I install encore-code-review in Claude?
Run npx skills add encoredev/skills --skill encore-code-review -a claude-code. The skill lands in ~/.claude/skills/code-review/SKILL.md.
What does encore-code-review do?
It checks Encore.ts code against a checklist: package-level infrastructure, imports, error handling, SQL, subscription idempotency. You get findings tagged by severity with file and line.
What does the skill require to work?
It targets Encore.ts and its modules encore.dev/api, encore.dev/pubsub and ~encore/clients. On another framework the checks won't match your code.
Source text
SKILL.md · Apache-2.0
---
name: encore-code-review
description: Review existing Encore.ts code for best practices and common anti-patterns.
when_to_use: >-
User is reviewing a pull request, auditing existing code, or checking for Encore-specific anti-patterns before merging — infrastructure declared inside functions, missing service files, wrong import paths, raw `Error` thrown instead of `APIError`, untyped APIs. SKIP for greenfield code being actively written. Trigger phrases: "audit", "review", "before merge", "PR review", "anti-patterns", "code smell", "lint this".
---
# Encore Code Review
## Instructions
When reviewing Encore.ts code, check for these common issues:
## Critical Issues
### 1. Infrastructure Inside Functions
```typescript
// WRONG: Infrastructure declared inside function
async function setup() {
const db = new SQLDatabase("mydb", { migrations: "./migrations" });
const topic = new Topic<Event>("events", { deliveryGuarantee: "at-least-once" });
}
// CORRECT: Package level declaration
const db = new SQLDatabase("mydb", { migrations: "./migrations" });
const topic = new Topic<Event>("events", { deliveryGuarantee: "at-least-once" });
```
### 2. Using require() Instead of import
```typescript
// WRONG
const { api } = require("encore.dev/api");
// CORRECT
import { api } from "encore.dev/api";
```
### 3. Wrong Service Import Pattern
```typescript
// WRONG: Direct import from another service
import { getUser } from "../user/api";
// CORRECT: Use ~encore/clients
import { user } from "~encore/clients";
const result = await user.getUser({ id });
```
### 4. Missing Error Handling
```typescript
// WRONG: Returning null for not found
const user = await db.queryRow`SELECT * FROM users WHERE id = ${id}`;
if (!user) return null;
// CORRECT: Throw APIError
import { APIError } from "encore.dev/api";
const user = await db.queryRow`SELECT * FROM users WHERE id = ${id}`;
if (!user) {
throw APIError.notFound("user not found");
}
```
### 5. SQL Injection Risk
```typescript
// WRONG: String concatenation
await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// CORRECT: Template literal with automatic escaping
await db.queryRow`SELECT * FROM users WHERE email = ${email}`;
```
## Warning Issues
### 6. Missing Type Annotations
```typescript
// WEAK: No explicit types
export const getUser = api(
{ method: "GET", path: "/users/:id", expose: true },
async ({ id }) => {
return await findUser(id);
}
);
// BETTER: Explicit request/response types
interface GetUserRequest { id: string; }
interface User { id: string; email: string; name: string; }
export const getUser = api(
{ method: "GET", path: "/users/:id", expose: true },
async ({ id }: GetUserRequest): Promise<User> => {
return await findUser(id);
}
);
```
### 7. Exposed Internal Endpoints
```typescript
// CHECK: Should this cron endpoint be exposed?
export const cleanupJob = api(
{ expose: true }, // Probably should be false
async () => { /* ... */ }
);
```
### 8. Non-Idempotent Subscription Handlers
```typescript
// RISKY: Not idempotent (pubsub has at-least-once delivery)
const _ = new Subscription(orderCreated, "process-order", {
handler: async (event) => {
await chargeCustomer(event.orderId); // Could charge twice!
},
});
// SAFER: Check before processing
const _ = new Subscription(orderCreated, "process-order", {
handler: async (event) => {
const order = await getOrder(event.orderId);
if (order.status !== "pending") return; // Already processed
await chargeCustomer(event.orderId);
},
});
```
### 9. Secrets Called at Module Level
```typescript
// WRONG: Secret accessed at startup
const stripeKey = secret("StripeKey");
const client = new Stripe(stripeKey()); // Called during import
// CORRECT: Access inside functions
const stripeKey = secret("StripeKey");
async function charge() {
const client = new Stripe(stripeKey()); // Called at runtime
}
```
## Review Checklist
- [ ] All infrastructure at package level
- [ ] Using ES6 imports, not require()
- [ ] Cross-service calls use `~encore/clients`
- [ ] Proper error handling with APIError
- [ ] SQL uses template literals
- [ ] Request/response types defined
- [ ] Internal endpoints have `expose: false`
- [ ] Subscription handlers are idempotent
- [ ] Secrets accessed inside functions, not at import time
- [ ] Migrations follow naming convention (001_name.up.sql)
## Output Format
When reviewing, report issues as:
```
[CRITICAL] [file:line] Description of issue
[WARNING] [file:line] Description of concern
[GOOD] Notable good practice observed
```
© encoredev · Apache-2.0 · encoredev/skills