CLAUDE.md as a Briefing

CLAUDE.md as a Briefing

Article 1 · Series: Agentic Coding with Claude Code

Whoever sends Claude Code into a project is sending an agent without memory. What the model brings along is general training — what holds in this concrete project has to be handed over again at the start of every session. That is exactly what CLAUDE.md is for: a file that is loaded automatically before the first command runs. Stack, language, directory taboos, style rules, what is allowed and what is not. A situation report, not a contract — precise enough so the agent knows what it is getting into, and concise enough not to paralyze it.

The image behind it is that of a briefing before deployment: what is the situation, what tools are at hand, what is the goal, what is off-limits. Without this briefing, the agent improvises out of general model knowledge. With it, it sets out and knows what to do.

In this first article of the series, we set up the complete working environment for byhaushalt — a project that aims to make the Bavarian state budget 2026/27 machine-readable and accessible from the official PDFs. We install Claude Code, create the repository on Codeberg, write CLAUDE.md, set permissions, and check in seventeen budget documents. At the end stands tag v0.1 — a clean starting point we will pick up in the following articles. Two approaches we discarded along the way travel with us as well: not as a warning, but because they belong to the actual sequence of events.

Installing Claude Code — CLI, App, and Remote

Let us start at the beginning, and that is Claude Code itself. This article series runs on a Mac M3 or M4, but it works the same on Linux. With the OS parody from Redmond — aka Windows — it should be just as possible, but it is not covered separately. (Sorry, but anyone developing on Windows is beyond help anyway.)

CLI vs. App

Claude Code is available in two local distributions. The CLI comes via npm:

npm install -g @anthropic-ai/claude-code
claude

or, if you have brew installed on your Mac:

brew install claude
claude

On first launch an OAuth flow with the Anthropic account follows, then you land in an interactive shell. No daemon, no separate background process — Claude Code starts, works, exits.

The desktop app (claude.ai/download) is the second option. It brings the same agent loop into a native window, complemented by a visual representation of tool use: you see which files are read, which Bash commands are executed, where permissions kick in. For getting started this is helpful — Claude Code’s loop behavior becomes more tangible when you can follow it live. The downside: the app is an additional application in the dock, not a terminal tool. Anyone already living in iTerm2 or another terminal switches context for it.

For this series the CLI is the primary distribution. The app stays optional — useful for observing, less so for the workflow itself.

Remote Control

Remote Control connects claude.ai/code or the Claude app (iOS/Android) to a locally running Claude Code session. Important: the agent runs on your own machine — code, repository, and executed commands stay local. The web interface and the mobile app are remote UI windows into this session, not a cloud agent, not a second instance.

You can activate it directly in a running session:

/remote-control byhaushalt

Or at startup via the CLI:

claude --remote-control "byhaushalt"

If you want Remote Control on permanently for all sessions, the option is under /config → “Enable Remote Control for all sessions”.

What happens when the machine sleeps or the network drops? The session is interrupted — it is running locally, after all. Remote Control is, however, designed for reconnection: as soon as the machine is back online, the app rebuilds the connection automatically. No manual restart needed.

Typical use cases: kicking off a longer task from your phone, checking status in between, or letting a second person watch via claude.ai/code during a pair-programming moment. For byhaushalt we use this selectively — not as a central workflow element.

Distinction: This is to be distinguished from “Claude Code on the web” — a separate feature with real cloud sandbox sessions that need no local machine at all and can be moved between browser and terminal. For most scenarios in this series this is not relevant, but anyone running into the terms should know they are two different features.

Practical pattern for the series

CLI primarily, app optional for observing, remote selectively for tasks that should keep running while I am away. That holds for all articles in this series.

Why CLAUDE.md Comes First

Claude Code reads CLAUDE.md at the start of every session. Not optional, not configurable — that is the protocol. The file is therefore the only persistent memory between sessions that is not buried in Git history or code comments.

That sounds like documentation. It is not documentation. Documentation describes a state for human readers who can judge what is relevant and what is not. CLAUDE.md gives operational instructions to an agent that, without them, literally does not know what language comments should be written in, whether it may run git push --force, whether it may modify PDFs. Every missing convention is an implicit permission to do something.

For byhaushalt this was particularly concrete: without an explicit prohibition, Claude Code could have renamed, converted, or otherwise mutated the source data in daten/. These are official state budget documents we want to preserve — their integrity is non-negotiable.

What belongs in CLAUDE.md: stack decisions, directory structure, language rules, style rules, prohibitions. What does not belong in it, we will get to later.

Setting Up the Repo

First locally, then remote. git init in the project directory, then the basic structure: an empty README, .gitignore, and the first commit.

The .gitignore covers the usual suspects — macOS (.DS_Store), Python (__pycache__/, .venv/, *.pyc, .pytest_cache/, .ruff_cache/), Node (node_modules/, dist/), editor clutter (.vscode/, .idea/). Nothing unusual, but better early than after the first push with three caching directories in the repository.

The remote goes to Codeberg. Codeberg is a self-hosted Gitea deployment, run by a German non-profit, on solid ground from a data-protection perspective — for a project that processes official budget data, a more sensible choice than GitHub. Repo creation on Codeberg is simple: name, visibility, no automatic README (that comes from the local repo). Set the remote, push the first commit, done.

That the first attempt landed under the wrong name — more on that later.

Writing CLAUDE.md

The finished file for byhaushalt looks like this:

# byhaushalt — Project Conventions for Claude Code

byhaushalt extracts the official budget plans of the Free State of Bavaria (PDF) and visualizes them as an interactive web application. Demo project for the series "Agentic Coding with Claude Code" on rotecodefraktion.de.

## Stack

- Parsing: Python 3.12, uv, pdfplumber, polars
- Data format: Parquet + JSON manifest
- Frontend: Vite + React 19 + TypeScript + Tailwind + shadcn/ui
- Charts: Observable Plot
- Tests: pytest + Hypothesis (Python), Vitest + React Testing Library (Frontend), Playwright via MCP (E2E)

## Directories

- `daten/` — source PDFs, pinned, do not modify
- `parser/` — Python code, uv-managed
- `web/` — frontend
- `docs/` — architecture and ADRs
- `.claude/` — skills, commands, hooks, settings

## Language

- Code identifiers: English
- Comments: only when non-obvious (why, not what)
- Docs in `docs/`: German
- Commit messages: German or English, concise, Conventional Commits style
- Tests: German test descriptions allowed

## Style rules

- No ASCII diagrams — Excalidraw or Plot
- No filler comments
- No TODO comments without an issue reference
- Type hints in Python everywhere
- TypeScript strict mode

## Prohibitions

- No direct access to web APIs without permission
- No destructive Git actions without explicit instruction
- No `--no-verify` on commits
- Do not modify PDFs in `daten/`, only read

## Test discipline

- Test-first: write the test first, check it fails, then implement
- On implementation: show output before commit

The categories are not arbitrary. Stack gives Claude Code the context of which tools are available and which are not — no requests instead of httpx, no pandas instead of polars. Directories pins down what lies where and — in the case of daten/ — what must not be touched. Language prevents comments from landing in English, commit messages in German, and test descriptions somewhere in between. Style rules are not aesthetic questions — filler comments like # set variable x are noise read along at every review.

The prohibitions section deserves particular attention. Prohibitions in CLAUDE.md are not decorative. They are a first line — the second line are the permissions in .claude/settings.json. git push --force and --no-verify are not phrased there as hints but as explicit negations. An agent that crosses these boundaries does so not out of malice but because nothing stands in the way. The wording in CLAUDE.md makes it clear that these boundaries are boundaries.

What Does Not Belong in CLAUDE.md

Three categories that regularly land in CLAUDE.md and do not belong there:

Architecture details — how the parser is structured, which abstraction layers exist, why we chose Parquet over SQLite. That belongs in docs/architecture.md or in Architecture Decision Records. CLAUDE.md describes conventions, not states. The architecture can change; the convention for how changes are documented stays stable.

TODOs — open items belong in issues or pull requests, not in CLAUDE.md. A TODO in the convention file suggests it holds permanently. An issue has a status, an assignee, a discussion.

Build instructions for humansuv sync, pnpm install, pnpm dev. These belong in the README or in lockfiles. CLAUDE.md assumes the human knows the toolchain; it is not an onboarding document.

The rule of thumb: conventions yes, states no. Whatever could change with every commit does not belong in CLAUDE.md.

Permissions in .claude/settings.json

The second file in the .claude/ directory is .claude/settings.json. It defines what Claude Code is allowed to do at system level — independent of what stands in CLAUDE.md. The settings are a harder boundary: CLAUDE.md can be rephrased, misinterpreted, or ignored; permissions in settings.json are enforced by the tool.

The relevant excerpt for byhaushalt:

{
  "permissions": {
    "allow": [
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git commit:*)",
      "Bash(git tag:*)",
      "Bash(git push:*)",
      "Bash(uv:*)",
      "Bash(pytest:*)",
      "Bash(ruff:*)",
      "Bash(pnpm:*)",
      "Read(*)",
      "Edit(*)",
      "Write(*)"
    ],
    "deny": [
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)",
      "Bash(git reset --hard:*)",
      "Bash(curl:*)",
      "Bash(wget:*)"
    ]
  }
}

The allow list is explicit. Bash(git push:*) allows push, Bash(git push --force:*) in the deny list blocks the destructive variant — both at once, the more specific deny rule wins. curl and wget are blocked: byhaushalt should not pull data from the network without explicit clearance. Reading, writing, and editing files are broadly allowed — that is the agent’s core business.

The deny list is a second layer, not a redundant one. Even if the allow list were phrased too broadly, the deny entries catch it. Both lists together form a security model appropriate for an analysis project with sensitive source data.

What you should not do: –dangerously-skip-permissions

Claude Code knows a CLI flag called --dangerously-skip-permissions. The name is not a coincidence — Anthropic chose it deliberately. Whoever sets the flag turns off all permission prompts. Allow list, deny list, everything we just configured in settings.json — ignored. The agent can then delete files, push commits, and make arbitrary network calls without asking back.

There are contexts in which this is legitimate: in a Docker container or an isolated VM where the workspace can be deliberately thrown away, or in a CI pipeline without access to production systems. Whoever knows exactly what they are doing and wants to release the brake briefly, knowingly, knows the risk.

For everyone just starting with Claude Code: leave the flag off. Not because a tool would be unnecessarily paranoid, but because the entire protective mechanism we are building up in this series — permission structure, deny list, CLAUDE.md instructions — is deactivated by a single flag. byhaushalt does not use --dangerously-skip-permissions. Anyone rebuilding the projects in this series: same.

Source Data into the Repo

byhaushalt works with 17 official PDFs: the overall budget 2026/27 and the individual plans 01 through 16. They come from the Bavarian State Ministry of Finance, are in the public domain, and live in daten/.

We checked them in directly — no Git LFS, no external storage solution. The total size is around 22 MB. That is no problem with modern Git hosting limits, and the upside is considerable: a git checkout v0.1 gives a new contributor — or Claude Code in a new session — the complete data set immediately, without resolving external dependencies. Reproducibility here is not an academic virtue but a practical necessity: when the parser is calibrated against a particular PDF state, that exact state should still be retrievable in six months.

The prohibition from CLAUDE.md takes hold here directly: Do not modify PDFs in daten/, only read. The deny rule for rm -rf:* makes it more robust at the system level. The source data are the immutable ground on which everything else builds.

First Day — v0.1

After four commits we had a state that deserved a tag: conventions, permissions, data — no application code.

git tag -a v0.1 -m "Init: CLAUDE.md, permissions, source data (17 PDFs)"
git push origin v0.1

The practical value: v0.1 is a named reference point. If a convention changes in a later article, you can cleanly say “from v0.3 onwards this is called differently”. For readers who want to follow the series, the tag is the entry point — clone, checkout, done.

State at the End of This Article

The repo now contains: four commits, one tag, 17 PDFs, three configuration files — CLAUDE.md, .claude/settings.json, .gitignore — and no application code.

To follow along:

git clone https://codeberg.org/rotecodefraktion/byhaushalt.git
cd byhaushalt
git checkout v0.1

The complete state lives at byhaushalt @ v0.1.

Permissions wildcard. The first draft of settings.json had Bash(*) in the allow list — everything allowed, deny list as the only restriction. That looked more compact. It is, however, the worse decision: a wildcard allow list implicitly tells the agent that anything not explicitly forbidden is desired. An explicit allow list says what is desired. The difference sounds philosophical but has practical consequences in unexpected situations — if Claude Code wants to run a command that is in neither the allow nor the deny list, the behavior under Bash(*) differs from that under an explicit list. We switched to the explicit variant. It is longer, but clearer.

Where We Go Next

The next article is about Plan Mode and Subagents. Before a single line of parser code is written, the PDF structure has to be mapped out: how are the budget plans built, which page types exist, where do the tables begin, where do they end. That is an analysis task — and exactly the case Plan Mode was built for.

For an introduction to the basics of the series — Vibe Coding vs. Agentic Coding, tool use, loop, verification — the prologue is the right starting point.