Working with Claude Code in production has become normal for me. We are in a position where this is possible, and since I am an interested developer, I am keen to learn about what really works and what does not.
I can say, there is a lot on both sides! 😀
First, some context and numbers
We are using Claude Code in a 160,000-line codebase, which is an above-average Next.js application, handling the front-end part of a larger application.
Something I recognized was that most tasks we run with Claude Code right now are basically above $1. The most significant task is now approaching $40, which is one I want to talk about today. On average, I see something like $6–8.
Even smaller tasks like the following range around one Dollar.
Task 1: Add showPrice
Add IPricingTierLayoutConfig.t1ShowPrice: boolean
Add IPricingTierLayoutConfig.t2ShowPrice: boolean
Add IPricingTierLayoutConfig.t3ShowPrice: boolean
Implement as <Toggle> CMSLayoutPricingTier2.tsx
Effect: Will show the price in the TierBox.tsx; or not.
Additional Context:
ReadMe.CMSLayoutTier.md
Of course, the costs for the API were slightly increased, but in general, it’s because Claude Code is looking up a lot of things by itself, which is one of the main drivers for costs with agentic coding in my own experience.
The following aspects are the main drivers for cost
An untidy, unstructured code base that doesn’t follow a clear line
The lack of detail in specific context files.
Too large tasks (Don’t dare to vibe!)
1) Keep your codebase clean and understandable.
What you REALLY need to take care of is that your codebase follows an intention and a clear line of thought.
Having components, classes, and functions scattered throughout, and all are built up differently, makes it hard to pack into clear contextual prompts.
Agents and LLMs will look up things by themselves when they don’t understand something enough.
❌ This can quickly make 90% of your token usage per prompt!
2) Additional specific context is key
We have a lot of context files, which help us to maintain an efficient workflow with Claude Code. It’s not perfect, but it removes the need to scan and read through many files to find what you were talking about in your prompt 😀
Here comes in, why a clean code base (1) is so important. You cannot really describe a file structure, component design, design system, etc., when those don’t follow general rules.
We have two types of contextual ReadMe files:
Feature-specific context (more complex feature-sets have their own docs)
General documentation like “How localisation is handled”, “Design system & UI Components), “How to handle state management”.
You can easily add an absolute path or just the filename in the prompt, as shown in listing “Task 1: Add showPrice".
Example: ReadMe.Localisations.md
In this example, the LLM finds all information about:
Translation structure and storage
Using the applyTranslation and getT hooks
State management with Zustand
Local-first translation loading strategy
Fallback logic for missing translations
Static vs. entity-based translations
Translation in navigation and menus
GraphQL integration for fetching and saving
Rich text translation support
Race condition prevention and loading guards
Best practices for maintainability and performance
With this information, you don’t need to explain how translation management is handled in the prompt itself. In claude.md, you just provide the directive to use localisation. Not how it works in detail, since this can become quickly too much information and token usage for simpler tasks.
We have just a simple wrap-up of how to apply translation (read/write) in the daily coding business in the claude.md itself. Which is often not enough, particularly when working extensively with translation features, adapters, APIs, etc.
3) Too large tasks!
When is a task too large?
Easy: When Claude Code is auto-context compacting, which happens when you reach 1–0% in the bottom right corner. And that is dangerous! ❌
It was added to Claude Code to make longer tasks possible, but it’s a trap and shall simulate a continuous flow of vibing – at least I think that should be that.
What really happens after compacting? – Dementia!
You basically work in the first iteration (before compacting) with Claude Code well together. It understands everything, has listed a great plan of what to do, and executed well.
You are happy.
Then it hits 0% … auto compacting context …
The “old” Claude Code buddy is gone. A new shift started, the new guy looks at the summary, and JUST continues with SO many gaps in knowledge, experience, and history to the last one.
The new guy basically starts to fetch a lot of files to understand what’s going on – again!
All the effort of effective prompting is gone, and the following happens:
Quality degrades rapidly
Costs skyrocket
API time goes up
Claude Code is building something different from what you asked for anymore.
Take this example:
You see the original prompt at the bottom of the page.
That task costs $18, took 55 minutes API time, and had a 2-hour walltime (not 19h, I took the screenshot the next day) with ≈198.3k tokens in total.
The important part was that the session was compacted 2 times during the session.
✅ The vast majority of code was researched, planned, and generated during the first 80% of the context. The costs were $2,80 so far – That was totally fine for me.
❌ After the first auto-compacting, the costs suddenly went up to approximately $12. Simple requests for corrections took 3-5 minutes, and the token counter increased steadily. I didn’t get the result, I had the feeling the LLM starts to lose it and misses the point of what we were doing here together.
❌ After the second auto-compacting, the costs finally went to $18. I was disappointed, not so much about the wasted money, but about my time and the poor quality of the output.
My learning:
I have been working with CC for months now, and I love it ❤️. I don’t want to miss it anymore. But you really need to understand how these things work and what the limitations are. There are a lot of constraints, I can tell you :)
The solution for us was to start to respect the following rules:
Keep a clean and streamlined codebase.
Clear, specific context, on point.
Aim for small tasks & iterations – avoid vibing at all.
Define your task before you start to prompt, and split it into sub-tasks.
Embrace Software Engineering practices!
TDD and agentic coding?
Test-Driven Development (TDD) is helping a lot as well. The idea of defining the test cases beforehand works well for describing what you want upfront. The resulting tests (often expressed in behavior) help the LLM to understand what the intention is even better.
With the generative power of LLMs, it’s no excuse anymore that TDD might cost more time upfront 😀 But I am not an evangelist here, it works without as well, but you need to test anyway – you know you need to test anyway, right? Right! Good.
Example of a bad prompt
The following was my “handwritten” prompt, which I usually keep in my IDE.
It is, of course, based on an entire system of ours that we use, and the task was to add a CMS Layout Block, which handles tier subscriptions and is totally modifiable.
This task didn’t include any new mutations, changes to APIs, or other core changes to the system.
The prompt is okay in its contents, and as I wrote above, the first iteration of the three was perfect. But it was too large.
In hindsight, it would have been better to split into subtasks and make each more detailed, like:
Define interfaces, enums, state, and test plan.
Implement the UI components test-driven.
Implement the editor modal functionality to define and mutate data.
Review & refactor.
TODO CMSLayoutPricingTier2.tsx
We want to create a modular pricing layout which can have the following features:
- monthly and yearly pricing
- 1 to 3 Pricing Tiers (6 in total, monthly and yearly)
- Every Pricing Tier does have a productID in the IContentLayoutItem.config.productID stored. We use CMSProductSelectorModal.tsx to pick the product.
- TierEditorModal.tsx which will be an <EditorModal (wide), where we can define the 1-3 tiers + the optional annual discount.
User Story:
The user sees 1-3 TierBoxes with a title and description, as well as Features (showInTierBox=true) listed with a circle CheckIcon and a price + t1PriceSubline (ex: per month, exl. Tax).
The user sees above that a Button for Monthly and Yearly Pricing, which toggles prices by discount.
One of the Tiers is highlighted as hero tier, which is the one that is selected by default. The user can select a different tier as hero tier.
Every TierBox can have a <Chip element with a label (t1ChipLabel).
Below every tier is a <Button with a translateable label which does NOTHING for now; ()=>{} void function. the productId will be later ordered here.
When a feature is marked showInTierBox=true, it will only be show ONCE. Then it's in t1, then don't show it in t2 and t3. There will be a static translations (create it for me) "pricingTierAllFromPreviousTiers".
TierEditorModal.tsx
- EditorModal with a wide layout
- Editable list with Tiers; Every tier is a IContentLayoutItem.subType = "pricingTierItem" and does have title (Feature name), content (optional description).
- Each pricingTierItem has a config of this IContentLayoutItem we have
IContentLayout.config // The Config of the entire layout
- t1ProductId: string | null // Product ID for Tier 1
- t2ProductId: string | null // Product ID for Tier 2
- t3ProductId: string | null // Product ID for Tier 3
- t1Price: number // Price in cents for Tier 1
- t2Price: number // Price in cents for Tier 2
- t3Price: number // Price in cents for Tier 3
- t1PriceSubline: string // Optional subline for Tier 1
- t2PriceSubline: string // Optional subline for Tier 2
- t3PriceSubline: string // Optional subline for Tier 3
- AnnualDiscountPercent: number // Optional annual discount in cents 0-100
- heroTier: "t1" | "t2" | "t3" // The hero tier which is highlighted
- t1ChipLabel: string // Optional label for Tier 1 Chip
- t2ChipLabel: string // Optional label for Tier 2 Chip
- t3ChipLabel: string // Optional label for Tier 3 Chip
pricingTierItem.config IContentLayoutItem.config // Item === Feature
- type: "text" | "price" (number in cents) | "checkbox"; // determines the type of the field and how it's displayed
- showInTierBox: boolean; // determines if the field is shown in the tier box or only in the list
- values: {t1, t2, t3} // What will be displayed per tier
useLayoutMapDefinition()
const PRICING_LAYOUT_MAPPING: ILayoutItemMapping[] = [
{ index: 0, subtype: 'pricingHeader', description: 'Pricing Header Section' },
{ index: 1, subtype: 'pricingTierBox1', description: 'Title + Content T1' },
{ index: 2, subtype: 'pricingTierBox2', description: 'Title + Content T2' },
{ index: 3, subtype: 'pricingTierBox3', description: 'Title + Content T3' },
{ index: 4, subtype: 'pricingTierComponent', description: 'Pricing Tier Component' },
];
For 0 Title + Content:
- <CMSElementTitle centered
- <CMSElementRichText centered
(see CMSLayoutFeaturesGrid1.tsx)
EditorModal:
- Needs to be placed into StageWrapperInner.tsx (Lazy loaded) StageWrapperInner.tsx
- Needs to be placed into StageWrapperBlank.tsx (Lazy loaded) StageWrapperBlank.tsx
Zustand State:
We implement a Zustand state for open/close the modal and for the entire state. The state will be saved with a save button to mutation GQL later on.
Context:
- CMSLayoutFeaturesGrid1.tsx // only for patterns
ReadMe.ThemeSystem.md
ReadMe.DesignSystem.md
ReadMe.Localisations.md
Testing:
It is IMPORTANT to respect the testing patterns from claude.md.
Create a CMSLayoutPricingTier2.cy.tsx.
Work as close as to a "port" (Port/Adapter) as possible, and move mutations as up as possible.
UI/Admin:
- Only show when cmsControls & showFAB are both TRUE, otherwise read-only mode. No editing fields, no EditorModal
- Use <ButtonAdmin TSX "Edit Tiers"
Important:
- Respect the Standard Component Group (SCG) from claude.md
- Translate everything into pricingTiers.translation.ts
- All translations are locked, not not buttons are translatable. (Write translations), just pull the getT().
// Mutations
useContentLayoutItemCreateMutation()
useContentLayoutItemDeleteMutation()
useContentLayoutItemOrderUpdateMutation()
useContentLayoutItemUpdateMutation()
—Adrian