# A Working CLAUDE.md/AGENTS.md Template You Can Copy Today

> Source: <https://dev.to/dhrutika_rathod/a-working-claudemdagentsmd-template-you-can-copy-today-2dlh>
> Published: 2026-09-21 03:46:31+00:00

If you've added a `CLAUDE.md` or `AGENTS.md` file to your repo and felt like your coding agent still ignores half of it, you're not alone. Most of these files are just vague prose — "write clean code," "follow best practices," "use good error handling" — and vague prose doesn't change agent behavior any more than it changes a new hire's behavior on day one.

The fix isn't a longer file. It's a *differently structured* one. Below is a template you can copy straight into your repo today, plus the reasoning behind each section so you're not just cargo-culting it.

Here's the pattern almost everyone starts with:

```
## Code style
- Write clean, maintainable code
- Use good error handling
- Follow best practices
```

None of this is wrong, exactly. It's just useless to a model. "Good error handling" means nothing without a concrete shape to imitate. Compare that to this:

```
// Avoid
catch (e) {
  console.log(e);
}

// Preferred
catch (e) {
  logger.error('checkout.payment_failed', { orderId, cause: e });
  throw new PaymentError(orderId, e);
}
```

The second version gives the model an actual pattern to pattern-match against — a logger call with a namespaced event, structured metadata, and a typed error thrown upward. That's the single biggest lever in this whole exercise: **replace adjectives with code blocks.**

A good context file has six parts, in this order:

Let's build the file section by section.

```
---
last_updated: 2026-09-14
owner: platform-team
scope: global
review_cadence: quarterly
---
```

This looks like overhead, but it's the difference between a file that rots silently and one that gets maintained. When a rule looks outdated, whoever finds it knows exactly who to ping.

State the things a competent engineer would otherwise have to reverse-engineer from the codebase — especially anything that goes *against* the obvious default.

```
## Project context

- Stack: Node.js 20, Express, PostgreSQL (raw SQL via `pg`, no ORM — see ADR-014)
- Monorepo managed with pnpm workspaces
- Auth: sessions via `iron-session`, not JWTs — do not introduce JWT-based auth
- All API responses follow the envelope in `src/lib/response.ts`; never return raw objects from route handlers
```

Any model trained broadly will default to reaching for an ORM the moment it touches a database. Stating the constraint *and* pointing to the reasoning heads that off before it happens.

Pick your 3-5 highest-value conventions rather than trying to cover everything. More isn't better here; it's context rot.

**API responses**

```
// Avoid
res.json({ id: user.id, name: user.name });

// Preferred
res.json(successResponse({ id: user.id, name: user.name }));
```

**Async error handling**

``` js
// Avoid
app.get('/users/:id', async (req, res) => {
  const user = await getUser(req.params.id);
  res.json(user);
});

// Preferred
app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await getUser(req.params.id);
  res.json(successResponse(user));
}));
```

**Naming**

``` js
// Avoid
const d = new Date();
const u = await getUser(id);
function calc(x, y) { return x * y * 0.08; }

// Preferred
const requestTimestamp = new Date();
const user = await getUser(id);
function calculateSalesTax(subtotal, taxRate = 0.08) {
  return subtotal * taxRate;
}
```

**Dependency access**

``` js
// Avoid
import { db } from '../../../lib/db';

export async function getOrders(userId) {
  return db.query('SELECT * FROM orders WHERE user_id = $1', [userId]);
}

// Preferred
import { OrdersRepository } from './orders.repository';

export async function getOrders(userId, ordersRepo = new OrdersRepository()) {
  return ordersRepo.findByUserId(userId);
}
```

(Repos are injectable so tests can pass a fake — see `tests/api/orders.test.ts` for the pattern.)

Each pair takes about 30 seconds to write and saves you from re-explaining the same thing in code review, repeatedly, forever.

```
## Testing

- Every new route handler needs an integration test in `tests/api/`, following the pattern in `tests/api/users.test.ts`
- Run `pnpm test:unit` before considering any change complete
- Do not mock the database in integration tests — use the test containers setup in `tests/setup.ts`
- Minimum coverage for new files: 80%
## Do not touch

- `migrations/` — migrations are hand-reviewed only; never generate or edit these
- `src/legacy/billing/` — frozen code pending a rewrite; bug fixes only, no refactors
- `.github/workflows/` — CI changes require a platform-team review; flag instead of editing directly
```

If you've ever had an agent "helpfully" refactor a file that was explicitly untouchable, this section is why it happened — nobody told it not to.

```
## Commands

- Install: `pnpm install`
- Run dev server: `pnpm dev`
- Run all tests: `pnpm test`
- Lint: `pnpm lint`
- Type check: `pnpm typecheck`
---
last_updated: YYYY-MM-DD
owner: team-name
scope: global
review_cadence: quarterly
---

## Project context
- Stack: [languages, frameworks, database]
- Architecture: [monorepo/polyrepo, key services]
- Non-obvious constraints: [things that go against the default assumption]

## Conventions
### [Convention name]
Avoid:
[bad example]
Preferred:
[good example]
(repeat for 3-5 highest-value conventions)

## Testing
- [what every change requires]
- [how to run tests]
- [coverage or quality gates]

## Do not touch
- [path]: [reason]

## Commands
- Install: [command]
- Dev: [command]
- Test: [command]
- Lint: [command]
```

Here's the same template filled in for a mid-sized ASP.NET Core Web API with EF Core and Clean Architecture:

```
---
last_updated: 2026-09-14
owner: payments-team
scope: global
review_cadence: quarterly
---
## Project context
- Stack: .NET 8, ASP.NET Core Web API, EF Core 8, SQL Server
- Architecture: Clean Architecture — Api/, Application/, Domain/, Infrastructure/
- CQRS via MediatR — every write is a Command, every read is a Query
- Do not call EF Core directly from controllers — always go through a MediatR handler
- Dependency injection only — no `new SomeService()` inside business logic
```

**Controllers stay thin**

```
// Avoid
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderDto dto)
{
    var order = new Order { CustomerId = dto.CustomerId, Total = dto.Total };
    _dbContext.Orders.Add(order);
    await _dbContext.SaveChangesAsync();
    return Ok(order);
}

// Preferred
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderCommand command)
{
    var result = await _mediator.Send(command);
    return CreatedAtAction(nameof(GetOrder), new { id = result.OrderId }, result);
}
```

**Nullable reference handling**

``` js
// Avoid
public string GetCustomerName(int id)
{
    var customer = _repository.Find(id);
    return customer.Name; // throws NullReferenceException if not found
}

// Preferred
public async Task<Result<string>> GetCustomerNameAsync(int id)
{
    var customer = await _repository.FindAsync(id);
    return customer is null
        ? Result.Failure<string>($"Customer {id} not found")
        : Result.Success(customer.Name);
}
```

**Async naming and cancellation**

```
// Avoid
public Task<List<Order>> GetOrders(int customerId)
{
    return _dbContext.Orders.Where(o => o.CustomerId == customerId).ToListAsync();
}

// Preferred
public Task<List<Order>> GetOrdersAsync(int customerId, CancellationToken cancellationToken)
{
    return _dbContext.Orders
        .Where(o => o.CustomerId == customerId)
        .ToListAsync(cancellationToken);
}
```

Every async method ends in `Async` and accepts a `CancellationToken` as the last parameter — enforced by an analyzer, so missing it fails the build, not just review.

A few things worth noticing about this version versus the generic one:

`DbContext` straight into a controller.`Result<T>` pattern
Run a five-minute experiment:

If the two diffs look nearly identical, your file isn't doing anything. If the second run correctly follows a convention the first one violated, you've got a working file.

The difference between a context file that gets ignored and one that actually shapes agent behavior isn't length or thoroughness — it's specificity. Adjectives don't transfer; code blocks do.

*Originally published at [Dhrutika's Blog](https://dhrutika.github.io/portfolio/blog/agents-md-template.html) — I write about .NET, Angular migrations, and AI-assisted development.*
