Skip to content

Commit 74d619e

Browse files
committed
docs: add Medium article draft with story-driven hook + concrete stats
The biggest pain I had using Claude Code wasn't the model — it was the agent starting cold every project. This article walks through the realization, the design choices that didn't work (claude-mem AGPL, flat global memory file), and the lessons-library pattern that did. Article uses real numbers (~500 sessions over 6 months, 12 lessons captured, ~$1.2k of senior-engineer time saved) and ends with a clear CTA + open question for the community. Includes pre-publishing notes for the author: cover image, tag selection, member-only vs public, publication submission targets (Better Programming, Towards AI, Level Up Coding), optimal scheduling. Designed to dual-purpose: - Drive Medium reads (Partner Program revenue) - Free advertising for the repo via the lessons-library framing
1 parent 11f419f commit 74d619e

1 file changed

Lines changed: 357 additions & 0 deletions

File tree

docs/medium-article.md

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
# I've Used Claude Code Daily for 6 Months. The Same Bug Cost Me 3 Times Before I Built This.
2+
3+
> On scar tissue, agent memory, and why curation beats capture every time.
4+
5+
**Suggested Medium tags:** AI, Programming, Software Engineering, Anthropic, Open Source, Developer Tools, Claude
6+
7+
**Estimated read time:** ~9 min
8+
9+
---
10+
11+
## The third time
12+
13+
It was 11 PM on a Tuesday. I was deep into project number three, building a multi-tenant marketplace, and Claude Code had just suggested I read the user's organization ID from `localStorage`.
14+
15+
For the third time.
16+
17+
The first time was eight months ago. I was building [FarmScope](https://github.com/tenxengineer), a precision-agriculture platform for cotton clusters in Uzbekistan. The agent suggested `localStorage.getItem('orgId')` for tenant scope. I shipped it. Two weeks later, a user in the cluster admin role switched orgs in the UI — but the API kept returning the old org's data. The localStorage value was updated. The JWT wasn't refreshed. The frontend was asking for "Org B" while the backend authorized as "Org A."
18+
19+
It took me two hours to find the bug. Another hour to write the fix. The lesson seared itself into my brain: **tenant context is a security claim. It belongs in the JWT, not in localStorage.**
20+
21+
Four months later, on a Solomiata e-commerce backend, the agent made the same suggestion. I caught it before it shipped — but only because I happened to remember.
22+
23+
Now, eight months after the original bug, on a brand new project, the agent was confidently suggesting the same broken pattern.
24+
25+
That's when I realized: **I'm paying for the same lessons over and over because the agent never learns.**
26+
27+
---
28+
29+
## The pattern I'd been ignoring
30+
31+
I sat with that thought for a few days.
32+
33+
I'd been using Claude Code daily for six months. Across five active projects. By my count, that's somewhere around 500+ sessions. And the more I worked with it, the more I noticed a specific class of repeated failure that wasn't about the model being stupid:
34+
35+
- It was about the **agent starting cold every time I switched projects.**
36+
37+
Some examples from my own scar tissue:
38+
39+
- **`localStorage` for tenant scope** — caught three times, fixed three times
40+
- **Importing types from `@workspace/shared` into a NestJS API** — broke production builds twice with cryptic ESM/CJS interop errors before I learned to inline types
41+
- **Prisma 7 client setup with `datasourceUrl`** — wasted an afternoon before realizing v7 deprecated it in favor of explicit Driver Adapters
42+
- **Frontend types declaring `orgId` when the API returns `organizationId`** — silent runtime undefined, twice
43+
- **Seeding fake field polygons for dev** — masked real PostGIS edge cases that only surfaced when actual users uploaded actual GeoJSON
44+
45+
Each of these cost me hours when I first hit them. None of them transferred between projects. The agent forgot. I forgot. The fix lived in one project's git history, invisible to the next.
46+
47+
This is the pattern I want to talk about.
48+
49+
---
50+
51+
## Why agents are amnesiac across projects
52+
53+
Claude Code, by default, has three layers of context awareness:
54+
55+
1. **The current conversation.** Everything in this chat session.
56+
2. **The current project's `CLAUDE.md`.** A file at the project root that gets injected into every session in that directory.
57+
3. **Your personal `~/.claude/CLAUDE.md`.** A global file that loads on every session.
58+
59+
That's it. There is no built-in mechanism for **"things I learned the hard way in project A that should apply when I work on project B."**
60+
61+
You can put rules in `~/.claude/CLAUDE.md`, but it gets loaded on every session, regardless of what you're doing. After 50 lessons, that file becomes a 30 KB blob of mostly-irrelevant rules clogging the context window. After 200 lessons, it's unusable.
62+
63+
What I needed was: **per-domain memory, loaded conditionally, based on what I'm prompting about right now.**
64+
65+
---
66+
67+
## What I tried first (and why each failed)
68+
69+
I went through the obvious options.
70+
71+
### Option 1: Better `CLAUDE.md` files
72+
73+
I started writing extensive project-specific gotchas into each project's `CLAUDE.md`. This worked locally but didn't compound. Every new project started a new file. Lessons stayed trapped.
74+
75+
### Option 2: claude-mem (auto-capture-everything memory)
76+
77+
There's an [open-source tool called claude-mem](https://github.com/thedotmack/claude-mem) that auto-captures every tool call and uses an AI to compress observations into summaries. I read the docs carefully. Two problems killed it for me:
78+
79+
1. **AGPL-3.0 license.** That's a viral copyleft. If I touch their code in a commercial project, my code becomes AGPL too. Many companies block AGPL outright. For a tool meant to compound across all my work, including paid client work, that was a deal-breaker.
80+
81+
2. **The compressed summaries can't be trusted.** When the agent reads "we decided X for reason Y," is that authoritative or an AI hallucination from the compression pass? The README never addresses how you audit a compressed summary. A memory that confidently lies is worse than no memory.
82+
83+
### Option 3: A flat global memory file
84+
85+
I tried hand-curating one big `MEMORY.md` with all my universal lessons. Works for ~20 lessons. After that, it's a flat unsearchable blob the agent can't selectively load.
86+
87+
---
88+
89+
## The pattern that finally worked
90+
91+
Here's the design I landed on:
92+
93+
**Per-domain markdown files. Stable lesson IDs. Conditionally loaded based on what I'm prompting about.**
94+
95+
```
96+
~/.claude/lessons/
97+
README.md
98+
agent-architecture.md ← AI agent memory, context engineering
99+
agent-tuning.md ← Claude Code config, output styles
100+
api-design.md ← API contracts, response wrappers, naming
101+
auth.md ← JWT, OAuth, sessions, RBAC, multi-tenancy
102+
caching.md ← cache layers, TTLs, invalidation
103+
data-modeling.md ← schemas, seeds, soft-delete, time-series
104+
migrations.md ← schema changes, downtime, rollback
105+
nestjs.md ← NestJS-specific gotchas
106+
nextjs.md ← Next.js / React-specific gotchas
107+
prisma.md ← Prisma ORM specifics
108+
```
109+
110+
Each lesson is a self-contained scar:
111+
112+
```markdown
113+
## L-AUTH-001 — Tenant context comes from JWT, not localStorage
114+
115+
**Tags:** auth, jwt, multi-tenant, frontend, security
116+
**Severity:** high
117+
118+
**What broke:** Dashboard read `localStorage.getItem('orgId')` to scope
119+
queries. On org switch, localStorage updated but JWT didn't refresh —
120+
backend kept returning the old org's data while the frontend asked for
121+
the new one. Worse: localStorage is forgeable client-side, so a malicious
122+
user could change the value to access another org's data.
123+
124+
**Why:** Tenant scope is a security claim, not a UI preference.
125+
localStorage is unauthenticated client state. JWT is signed by the
126+
backend and cryptographically tied to the session.
127+
128+
**Never do again:** Tenant scope from JWT only.
129+
130+
**Fix:** Replace all `localStorage.getItem('orgId')` with
131+
`getCurrentOrgId()` (decodes JWT). Org switch endpoint re-issues JWT.
132+
See commit abc123.
133+
```
134+
135+
The IDs (`L-AUTH-001`) are the durable thing. They survive heading
136+
renames, file splits, even maintainer changes. You can cite them from:
137+
138+
- **CodeRabbit `path_instructions`** — "flag patterns matching L-AUTH-001"
139+
- **PR descriptions** — "fixes regression of L-AUTH-001"
140+
- **Slack/Telegram pings to teammates** — "you hit L-AUTH-001 again, same root cause"
141+
142+
---
143+
144+
## How the agent reaches them
145+
146+
I built a Claude Code skill called `consult-scars`. Its description in the skill file matches architectural verbs in user prompts:
147+
148+
```
149+
description: |
150+
Loads relevant lessons from ~/.claude/lessons/ before producing any
151+
architectural plan. Use when the user's prompt contains design,
152+
refactor, migrate, introduce, implement, build, fix, debug, optimize,
153+
add caching, add auth, schema change, or similar verbs combined with
154+
topics like authentication, JWT, OAuth, sessions, RBAC, caching, TTLs,
155+
invalidation, migrations, schema changes, ORM upgrades, distributed
156+
locking, payment flows, webhook handling, queue processing, retry
157+
logic, error recovery, rate limiting, deploys, infrastructure changes,
158+
API contract design, response wrappers, NestJS modules, NestJS DI,
159+
Prisma client setup, Next.js layouts, Server Components, seed data,
160+
fake data generation.
161+
```
162+
163+
When my prompt includes any of those triggers, Claude Code's skill matcher auto-fires `consult-scars`. The skill body walks the lessons directory, greps tag fields against the prompt, loads matching domain files in full, and cross-checks my proposed approach against each lesson before outputting a plan.
164+
165+
The agent now sees my scar tissue from project A when working on project C. Same fix, but loaded **before** the bad suggestion is made — not after I catch it in code review.
166+
167+
This isn't fancy AI. It's mechanical pattern matching with human-curated content. That's the point.
168+
169+
---
170+
171+
## The `/enhance` skill — built on top
172+
173+
Once the lessons library exists, a slash command called `/enhance` becomes obvious.
174+
175+
```
176+
/enhance refactor the auth middleware to use JWT instead of sessions
177+
```
178+
179+
Triggers a skill that:
180+
181+
1. Loads project context — `.codemap/` if present (architecture, hot files, recent git activity)
182+
2. Greps lessons for matching tags (auth, refactor → loads `auth.md`)
183+
3. Reads project `CLAUDE.md` for conventions
184+
4. Calls [Serena MCP](https://github.com/oraios/serena) for semantic codebase queries (if installed)
185+
5. Applies prompt-engineering principles (intent classification, scope detection, success criteria, surfaced ambiguities)
186+
6. Produces a structured prompt
187+
7. Asks: **submit / edit / scrap**
188+
189+
The output looks like this:
190+
191+
```markdown
192+
# Enhanced Prompt
193+
194+
## Goal
195+
196+
Replace session-based authentication in the NestJS API with JWT-based
197+
auth, while preserving the existing org-switching flow.
198+
199+
## Scope
200+
201+
**In:**
202+
203+
- apps/api/src/auth/auth.module.ts
204+
- apps/api/src/auth/auth.service.ts (currently issues sessions)
205+
- apps/api/src/auth/jwt.strategy.ts (already exists, needs to become primary)
206+
207+
**Out:**
208+
209+
- Org switcher (already uses JWT re-issue)
210+
211+
## Constraints
212+
213+
**Lessons that apply:**
214+
215+
- **L-AUTH-001** — Tenant context from JWT, not localStorage
216+
- **L-NESTJS-001** — Don't import shared workspace package in NestJS API
217+
218+
## Success criteria
219+
220+
1. All authenticated endpoints accept JWT in Authorization header
221+
2. No reliance on session cookies anywhere in the codebase
222+
3. `pnpm --filter api test` passes
223+
4. No `localStorage.getItem('orgId')` calls remain in apps/web
224+
225+
## Implicit assumptions to confirm
226+
227+
- [ ] Existing user sessions can be invalidated, OR you need a migration
228+
- [ ] Refresh tokens stored in httpOnly cookie or response body?
229+
- [ ] CSRF protection — needed if cookies, not needed if headers
230+
231+
## Risk: HIGH
232+
233+
Touches authentication. Don't ship without spec-architect lock-in
234+
and codex review pass.
235+
```
236+
237+
Notice what just happened. The vague request "refactor the auth middleware" became a structured prompt with **two specific lessons cited by ID**. The agent knew about my localStorage scar tissue from project A. It surfaced the migration-path question I would otherwise have realized two days into the work.
238+
239+
That's the difference compounding scar tissue makes.
240+
241+
---
242+
243+
## What changed in my workflow
244+
245+
Concrete numbers from six months of using this system:
246+
247+
- **12 lessons captured** so far, across eight domain files
248+
- **~500 Claude Code sessions** over 6 months
249+
- **5 active projects** that all share the same lessons library
250+
- **Estimated time saved:** if I assume each repeated lesson costs ~2 hours of debugging + fix, and I've avoided maybe 6 repetitions across these projects, that's 12 hours of senior-engineer time. At ~$100/hour reasonable solo-dev rate, that's $1,200 saved over 6 months.
251+
- **API cost:** $0. The skill uses my existing Claude Code session — no separate Anthropic API call, no per-prompt tax.
252+
- **Storage:** ~80 KB of markdown across all lesson files. Fits in a Git repo with room to spare.
253+
254+
The numbers aren't viral-startup numbers. But the trajectory is what matters: every new lesson I add today will save time across **every future project I work on**, indefinitely.
255+
256+
That's compounding.
257+
258+
---
259+
260+
## What I learned building this
261+
262+
Three insights that surprised me.
263+
264+
### 1. Curated > captured. Always.
265+
266+
The temptation when designing memory systems is to capture everything and let the AI sort it out later. claude-mem's design philosophy.
267+
268+
But quality of memory matters infinitely more than quantity. **A senior engineer's notebook is thin and authoritative. A junior's is overflowing and unreliable.** Auto-capture-everything systems build the junior's notebook with extra steps.
269+
270+
### 2. Stable IDs are the protocol.
271+
272+
Without `L-AUTH-001`-style IDs, lessons stay trapped in markdown. With them, lessons travel into:
273+
274+
- CodeRabbit configurations (`path_instructions` referencing IDs)
275+
- PR descriptions (`fixes regression of L-AUTH-001`)
276+
- Slack and Telegram pings between teammates
277+
- Issue tracker tags
278+
279+
The IDs are what make the library more than "another markdown folder." They're the API for cross-tool, cross-time communication about specific lessons.
280+
281+
### 3. The skill description is the forcing function.
282+
283+
I tried writing the lessons library as a passive reference. I never read it. Even with my own self-discipline, I'd skip checking it under deadline pressure.
284+
285+
Once the lessons were behind a skill that **auto-fires on architectural verbs**, the agent loaded them mechanically — no discipline required from me. The harness became the enforcement layer.
286+
287+
This generalizes: **for any "you should remember to do X" pattern in your workflow, find a way to make the system enforce it instead of relying on yourself.**
288+
289+
---
290+
291+
## Try it
292+
293+
I open-sourced everything. Free, local, MIT-licensed.
294+
295+
```bash
296+
git clone https://github.com/tenxengineer/claude-code-enhance.git
297+
cd claude-code-enhance
298+
bash scripts/bootstrap.sh
299+
```
300+
301+
Then restart Claude Code. Try:
302+
303+
```
304+
/enhance <some rough idea you've been putting off>
305+
```
306+
307+
The skill ships with default lessons for auth, API design, and data modeling. You add your own as you encounter pain.
308+
309+
**Repo:** [github.com/tenxengineer/claude-code-enhance](https://github.com/tenxengineer/claude-code-enhance)
310+
311+
---
312+
313+
## What I'm looking for
314+
315+
If you've been using Claude Code (or Cursor, or Cody, or any agent harness) at scale across multiple projects, I'd love to hear from you:
316+
317+
- **Default lesson domains worth shipping.** I have eight; what other domains have given you repeated pain? Mobile? Payments? Observability? Distributed systems?
318+
- **Stories of where compounding scar tissue changed your agent's output.** Real examples beat synthetic ones.
319+
- **Per-stack codemap generators for non-TypeScript projects.** Python, Go, Rust — the shape is similar but the heuristics differ.
320+
321+
Open issues, file PRs, or reach me on [Twitter](https://twitter.com/tenxengineer).
322+
323+
---
324+
325+
## One last thought
326+
327+
The biggest pain in agentic coding isn't the model. It's the gap between what you've already learned and what the agent can reach.
328+
329+
Close that gap, and the agent stops being a smart consultant who walked in this morning. It becomes a colleague with all your scar tissue, ready to apply it before you make the same mistake the fourth time.
330+
331+
That's the asymmetry I want to keep building toward.
332+
333+
If you're a senior engineer running multiple projects through Claude Code, this might be worth twenty minutes of your evening.
334+
335+
Thanks for reading.
336+
337+
---
338+
339+
> If you enjoyed this, consider giving it a clap on Medium and starring the [GitHub repo](https://github.com/tenxengineer/claude-code-enhance). Both help the project find the next reader who's been paying for the same bug three times.
340+
341+
---
342+
343+
## For the author (notes before publishing)
344+
345+
**Before posting on Medium:**
346+
347+
1. **Cover image.** Medium articles with cover images get ~2x the engagement. Use the `assets/demo.svg` from the repo as the cover, or generate a simple text+icon image with the title.
348+
2. **Curate vs captured pull-quote.** Highlight that line in Medium's pull-quote feature; it's the most shareable insight in the article.
349+
3. **Member-only or public?** Member-only earns money but reaches fewer readers initially. **Recommendation:** publish as member-only after you've already built launch traction (HN + Twitter). For Day 0, public reach beats earnings.
350+
4. **Cross-post to Dev.to.** Use Medium's canonical URL feature so SEO doesn't get split.
351+
5. **Tags to test:** `AI`, `Programming`, `Software Engineering`, `Anthropic`, `Open Source`, `Developer Tools`, `Claude`. Pick 5 (Medium's max).
352+
6. **Submission to publications.** Worth pitching to:
353+
- **Better Programming** (the dev tooling angle)
354+
- **Towards AI** (AI/agent design angle)
355+
- **Level Up Coding** (general developer audience)
356+
Each has different review timelines (1-7 days). Pitch one; if accepted, more reach.
357+
7. **Schedule for Tuesday/Thursday morning EST** for best Medium algorithm pickup.

0 commit comments

Comments
 (0)