Skip to content

What is lessons.md and How Does Claude Code Self-Improve?

Problem

Claude kept making the same mistakes across sessions. I’d spend time debugging an issue, explain the fix to Claude, and then two sessions later, it would make the exact same mistake again.

For example:

Session 1: Claude tried to run migrations without checking for pending transactions.
I explained: "Always check for pending transactions before ALTER TABLE"
Claude acknowledged and fixed it.
Session 2: Same project, different feature.
Claude tried to run migrations without checking for pending transactions.
Same error. Same debugging. Same explanation.

I had CLAUDE.md with rules and instructions, but it wasn’t enough. CLAUDE.md is great for defining how Claude should behave, but it doesn’t capture what Claude learned through actual mistakes.

What I discovered

I found a Reddit discussion about a pattern called lessons.md. The key insight was:

“lessons.md is a separate file from CLAUDE.md — it’s where the agent logs discoveries mid-task, not something you pre-populate.”

This was different from my approach. I had been writing all my knowledge into CLAUDE.md upfront. But lessons.md is meant to grow organically as Claude encounters issues.

The key difference
CLAUDE.md -> Instructions you write BEFORE Claude works
lessons.md -> Knowledge Claude writes AFTER making mistakes

The Reddit thread also highlighted a critical point:

“The real trick is making the write explicit in your instructions (‘before closing any task, update lessons.md’), because the model skips it when context gets heavy near the end of a session.”

This explained why my previous attempts at self-documenting failed. I didn’t explicitly instruct Claude to update the file before closing tasks.

How I set it up

I created a lessons.md file in my project root and added an explicit rule to CLAUDE.md:

CLAUDE.md (added section)
## Session Hygiene
Before closing any task:
1. Update lessons.md with any mistakes made
2. Document patterns you discovered
3. Note any gotchas specific to this codebase
Read lessons.md at the start of each session.

Then I created an empty lessons.md:

lessons.md (initial)
# Lessons Learned
This file is updated by Claude as it discovers patterns and mistakes.
Do not manually edit this file.

I started with an empty file intentionally. The Reddit comment was clear:

“Pre-populating lessons.md defeats the purpose. It should grow from agent work, not your writing.”

What happened

I tested this setup on a real project with a PostgreSQL database. Here’s what lessons.md looked like after a week:

lessons.md (after one week)
# Lessons Learned
## 2026-03-27: Database Migrations
- Never run migrations in production without backup
- Check for pending transactions before ALTER TABLE
- The users table has a trigger that needs disabling during migrations
- Always use --dry-run first with Django migrations
## 2026-03-25: API Response Format
- Always include success boolean in responses
- Error messages should be user-friendly, not stack traces
- Rate limiting returns 429, not 500
- The mobile app expects errors in {error: {code: string, message: string}}
## 2026-03-20: Frontend State
- Don't mutate state directly in React components
- Use immer for complex state updates
- Debounce search inputs to 300ms
- The search component has a race condition fix in useEffect cleanup
## 2026-03-18: Authentication Flow
- JWT tokens are stored in httpOnly cookies, not localStorage
- Refresh token endpoint is /auth/refresh, not /auth/token/refresh
- Failed auth returns 401, not 403 (backend quirk)

Notice how each entry came from a real mistake. The JWT token storage issue came from me wasting 30 minutes debugging why tokens weren’t in localStorage. Now Claude remembers this forever.

The workflow in action

Here’s how it works in practice:

Step 1: Session starts

Claude reads lessons.md automatically because of the rule in CLAUDE.md.

[Reading lessons.md from previous sessions]
Found 4 dated entries covering:
- Database migration patterns
- API response format requirements
- Frontend state management
- Authentication flow quirks

Step 2: Claude works

Claude applies the lessons during the session. For example, when I asked about implementing rate limiting:

Me: Add rate limiting to the user endpoint
Claude: I'll implement rate limiting. Based on lessons.md, I know that:
- Rate limiting returns 429 (not 500)
- Error format should be {error: {code: string, message: string}}
Implementing now...

Step 3: New mistake happens

During implementation, Claude makes a mistake:

Claude: Running migration to add rate_limit column...
Me: Error! Migration failed - lock timeout
Claude: Let me check... Ah, there are pending transactions on users table.
I should have checked this first.

Step 4: Claude updates lessons.md

At the end of the session, Claude updates lessons.md:

lessons.md (new entry added)
## 2026-03-27: Rate Limiting Implementation
- Always check for long-running queries before schema changes
- Lock timeout on users table can be resolved with: SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE query LIKE '%users%'

The key is the explicit instruction in CLAUDE.md. Without it, Claude skips this step when context gets heavy.

Why this matters

1. Persistent learning

Each session starts fresh, but lessons.md provides continuity. Mistakes that required debugging once get avoided in future sessions.

Without lessons.md:
Session 1: Debug migration lock issue (30 min)
Session 2: Debug same migration lock issue (30 min)
Session 3: Debug same issue again (30 min)
With lessons.md:
Session 1: Debug migration lock issue (30 min), write to lessons.md
Session 2: Read lessons.md, avoid issue (0 min)
Session 3: Read lessons.md, avoid issue (0 min)

2. Project-specific knowledge

CLAUDE.md contains general rules. lessons.md captures quirks specific to YOUR codebase:

General rule (CLAUDE.md):
- Check for pending transactions before migrations
Project-specific (lessons.md):
- The users table has a trigger that needs disabling during migrations
- JWT tokens are stored in httpOnly cookies, not localStorage
- Failed auth returns 401, not 403 (backend quirk)

3. Self-training without fine-tuning

You’re essentially training Claude on your specific project without any external tools or complex setups. The model literally trains itself on your codebase.

The open problem

The Reddit discussion raised a concern I’ve also noticed:

“If lessons.md grows forever, isn’t that just a second memory leak with better branding?”

This is a real problem. After a month, my lessons.md was 47KB. That’s context being consumed by potentially outdated information.

lessons.md growth over time
Week 1: 2KB - Quick wins
Week 2: 8KB - Still useful
Week 3: 20KB - Some redundancy
Week 4: 47KB - Noise starts appearing

There’s no documented best practice yet for culling lessons.md. I’ve been manually reviewing it weekly:

My curation process (manual, not ideal)
1. Review lessons.md at end of week
2. Remove entries that are now CLAUDE.md rules
3. Remove entries that are no longer relevant
4. Merge duplicate entries
5. Keep only codebase-specific quirks

This is the open question: How do you keep useful bits without creating a junk drawer?

Common mistakes I made

Mistake 1: Pre-populating lessons.md myself

WRONG approach
# Lessons Learned (my initial attempt)
- Always use TypeScript strict mode
- Never commit to main branch
- Write tests before code

This doesn’t work. These are general rules, not lessons from actual mistakes. They belong in CLAUDE.md, not lessons.md.

Mistake 2: Forgetting the explicit instruction

I initially just created lessons.md and hoped Claude would update it. It didn’t. The explicit rule in CLAUDE.md is critical:

Required in CLAUDE.md
Before closing any task:
1. Update lessons.md with any mistakes made

Mistake 3: Not checking if lessons.md is being used

I went two weeks before I realized Claude wasn’t reading lessons.md at session start. I added this verification step:

Added to CLAUDE.md
At the start of each session:
- Read lessons.md
- Summarize key learnings before starting work

Now I see confirmation that lessons are being loaded:

[Session start]
Reading lessons.md...
Key learnings: 4 categories, 12 total lessons
Ready to assist with your project.

Summary

In this post, I showed how the lessons.md pattern creates persistent memory for Claude Code sessions. The key differences from CLAUDE.md:

  1. CLAUDE.md: You write instructions before Claude works
  2. lessons.md: Claude writes lessons after making mistakes

The setup is simple but requires explicit instruction:

  1. Create empty lessons.md in project root
  2. Add rule to CLAUDE.md: “Before closing any task, update lessons.md”
  3. Let it grow organically from real work
  4. Manually curate to prevent context bloat

The pattern works, but the “junk drawer” problem remains unsolved. There’s no automated way yet to determine which lessons are worth keeping and which should be pruned.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments