Creating the first CLAUDE.md file is not much work as we saw in last week's post. However, getting an initial file and something that helps us is not necessarily the same. Why not use Claude to improve our CLAUDE.md file?
Use the current session of Claude Code
I like to use the plan mode a lot and when I experiment with some new ideas, my session may be a bit longer than usual. If this workflow fits you, make one small addition at the moment you think that you got a good amount of progress: Ask Claude to analyse this session and write a CLAUDE.md file for you:
# blog_postsSeries of small, standalone LangGraph examples — one per blog post — that
each illustrate ONE concept end-to-end. Examples must be runnable in
isolation and verifiable in a few `uv run` steps.
## Stack-Python 3.14, managed with `uv` (run with `uv run <file>.py`)
-`langgraph`, `langchain-openai`, `langgraph-checkpoint-sqlite` (see `pyproject.toml`)
-If a new example needs a dependency that isn't installed,
`uv add <pkg>` first.
## LLMLM Studio served locally on `http://localhost:1234`. Use this snippet
verbatim in every example that needs an LLM:
```pythonllm=ChatOpenAI(base_url="http://localhost:1234/v1",api_key="lm-studio",model="openai/gpt-oss-20b",temperature=0.1,) ```## File conventions-One Python file per example, named after the concept
(e.g. `short_term_memory.py`).
-Top-of-file docstring with a one-line description and the CLI commands.
-`sys.stdout.reconfigure(encoding="utf-8")` at the top of every script
— `gpt-oss-20b` emits emoji that crash on the Windows cp1252 console.
-`TypedDict` for graph state. Build the graph imperatively at module
level (no `build_graph()` helper), one statement per `add_node` /
`add_edge`:
```pythonworkflow=StateGraph(State)workflow.add_node("node_a",node_a)workflow.add_node("node_b",node_b)workflow.add_edge(START,"node_a")workflow.add_edge("node_a","node_b")workflow.add_edge("node_b",END)graph=workflow.compile(checkpointer=...,store=...) ``` For `SqliteSaver`, open a plain `sqlite3.connect(DB_FILE, check_same_thread=False)` at module level and pass it to `SqliteSaver(conn)` — the
`SqliteSaver.from_conn_string(...)` context manager doesn't fit
module-level init. Read `sys.argv[1]` and handle `reset` /
existence guards *before* opening the connection, since opening it
creates the DB file.
-No comments unless the WHY is non-obvious.
-Minimal abstraction. Duplication across files is fine; helpers go
in the file that needs them. Each example must read top-to-bottom
on its own.
-Made-up data is labelled (e.g. `"~12% kcal burn (made-up number)"`)
so readers don't mistake demo content for real research.
Try Claude Desktop
If you do want a template for your projects, it may be worthwhile to try Claude Desktop. The desktop application of Claude needs access to a folder where it can place the artefacts it produces, otherwise the required setup is minimal.
The benefit of Claude Desktop is that we can focus on the big picture and ask for current best practices when it comes to writing a CLAUDE.md file. When we write our prompt like that, we get a few dialogues that ask for more information and tailer the result exactly to our needs:
I need a good starting template for `CLAUDE.md` that I can use with my Python
projects. It should follow the best practices as of May 2026 and follow my
workflow. For example, I want to use uv for the package management, pytest
for testing and ruff to fix formatting. I want to use Git, use feature
branches and small commits that we merge to main if the work for a feature
is done and all tests passes. Can you guide me through all the necessary
questions I may have forgotten? /ultrathink /brutal
This approach takes more time and asks questions along the way before it creates a CLAUDE.md file. With the /brutal at the end Claude will tell you if you do something stupid and suggests an alternative, that you can follow or ignore:
Notebooks with "no special handling" will bite you. The moment a second person touches the repo — or you switch machines — you'll get merge conflicts on cell execution counts and base64-encoded plot outputs. The five-minute fix is nbstripout as a pre-commit hook; everything else stays the same. I'd revisit this the first time a notebook merge gets ugly.
With this advice included, I ended up with this CLAUDE.md file as a template for new Python data projects:
# PROJECT NAME<!-- 2–3 sentences: what this project is, who uses it, what it's NOT. -->
[One-paragraph description.]
## Tech stack-Python 3.13, managed by `uv` (commit `uv.lock` and `.python-version`)
-Test: `pytest` · Lint/format: `ruff` · Types: `mypy` (gradual)
-Config: `pydantic-settings` with `.env`-Notebooks: `nbstripout` git filter strips outputs
-Docstrings: NumPy style
## Project-specific commands ```bashuvsync--extradev# install with dev depsuvrunmypysrc/# gradual — fix new errors onlyuvrunnbstripout--install# one-time per clone ```Standard `uv run pytest`, `uv run ruff check . --fix`, `uv run ruff format .`work as expected.
## Layout ```src/<package>/ # importable code (src-layout)tests/ # mirrors src/ layoutnotebooks/ # outputs stripped on commitdata/ raw/ interim/ processed/ # gitignored samples/ # small committed fixturesscripts/ ```## Git workflow-`main` is always green: lint clean, formatted, all tests pass, no new mypy errors.
-Branches: `feat/…`, `fix/…`, `chore/…`, `exp/…`. Never commit directly to `main`.
-**One commit per sub-task.** Imperative mood. Never bundle unrelated changes.
-Before merging, all of these must pass:
```bashuvrunruffcheck.
uvrunruffformat--check.
uvrunpytest
uvrunmypysrc/
```-Merge with `git merge --no-ff` to preserve the feature-branch history as an
explicit merge commit. Delete the branch after.
## Configuration & secrets-Settings are a typed `pydantic-settings` class in `src/<package>/config.py`.
Import the `settings` instance; do not read `os.environ` in business logic.
-Use `extra="forbid"` so unknown env vars fail at startup.
-`.env` is gitignored; `.env.example` is committed and lists every variable.
-If a secret is committed, **rotate it** — removing the commit is not enough.
## DataOnly small samples committed; large files ignored.
```gitignoredata/**!data/**/!data/**/*.md!data/samples/** ````data/raw/` is immutable. Document provenance in `data/README.md`. Set random
seeds in any script using randomness.
## NotebooksOutputs are stripped by `nbstripout` (run `uv run nbstripout --install` once
per clone). Don't put production logic in notebooks — move stable code into
`src/<package>/` and import it. Restart-and-run-all before committing.
## Project don'ts-Don't `pip install` — always `uv add`.
-Don't commit `.env`, credentials, or files outside `data/samples/`.
-Don't bypass the merge gate with "I'll fix it next commit."
This is more focused on how we want to work and the technology stack we want to use than on the project itself. But that is exactly the purpose of a template: we can now adapt it to the new project and save a lot of repetition on how we want to work.
Revisit it from time to time
Claude Code quickly improves and with that the definition of a good CLAUDE.md file changes over time. Therefore, treat your CLAUDE.md file as a work in progress and use Claude every few months to check if it is still up to date.
Reference detailed instructions
A helpful little trick Claude Desktop showed me is that we can reference larger documents in our CLAUDE.md file. If we use this approach, Claude Code will only read these extra instructions when it needs them. In all other situations, that part is skipped.
We can reference more detailed instructions like this:
For the user interface, use the components described in @./UI_CONTROLS.md.
The referenced file itself is a plain Markdown file with more instructions. Just make sure that this file is also full of important content and not just a place you put all the stuff that does not fit anywhere else.
Keep experimenting
With a custom made CLAUDE.md file in place, we should immediately notice a difference. If our file works, we should need less corrections and no longer need to repeat ourselves to get the code written in the way we want it. If that is not the case or you want to go into a different way, keep experimenting.
Put the CLAUDE.md file into version control, change them while we build new features and only keep the changes if they are an improvement. Those improvements are the reason we do all the work and if it does not help us, we revert to a better working file.
Next
When we use Claude Desktop or our longer running session in Claude Code, we can turn our interactions into generalised CLAUDE.md files. Those files may not be perfect, but they offer a great starting point for the next round of improvements. And improving we must, when we want to stay current with Claude Code.
Next week we see how we can run Claude Code with a self-hosted LLM to prepare ourselves for changes in the pricing model.