Two Months of Claude Code: Getting the Most Out of It

Findings from using Claude Code after a few months plus some humourous customisations.

Two Months of Claude Code: Getting the Most Out of It
Photo by Chandler Cruttenden / Unsplash

The first post covered the experience: what changed, what surprised me, where the team ended up. This one is for engineers who have Claude Code installed and are wondering why it feels slightly off. The answer, in most cases, is configuration.


What the Defaults Get Wrong

The permission model is the first thing that needs fixing.

Out of the box you have two options: approve every tool call as it happens, or run in auto mode and trust it completely. Neither is workable for a senior engineer on a real product team. Approving everything manually means a constant stream of interruptions for operations that are obviously safe (reading a file, running grep, checking git status). Full auto mode means a tool with write access to your filesystem operating without any gates at all.

The solution is a middle layer. I maintain an allowlist of safe, read-only operations that run without prompting: find, grep, ls, cat, git log, git diff and a handful of others. For operations that actually matter, I run hooks. These are shell scripts that execute before the tool call and can block it if something looks wrong. A staging guard warns when Claude tries to run git add -A instead of staging specific files. A destructive git gate hard-blocks force pushes, git reset --hard and git clean.

The result is not zero friction. It is friction in the right places.

The second default that needs work: Claude will write Python for utility scripts. Java has had scripting capabilities since JDK 22. If you live in the JVM ecosystem there is no reason to context-switch to another language for a throwaway script, but Claude does not know that without being told.

Third: effort calibration. Ask Claude a simple question and you often get an architectural discussion in return. The defaults do not distinguish between a quick lookup and a significant implementation decision, and that lack of calibration is tiring to manage across a working day.

The common thread is that the defaults optimise for the average developer on the average task. If you are neither, you will need to configure your way out.


Configuring Your Way Out

The foundation of everything else is CLAUDE.md. Claude Code reads it at the start of every session and treats it as standing instructions. Write it once; Claude carries it into every conversation.

Two levels matter in a team context.

Global (~/.claude/CLAUDE.md): your personal config. Follows you into every project. Mine covers testing principles, Java conventions, preferred workflow and the pushback directives described below.

Project-level (.claude/CLAUDE.md): lives in the repository, committed to git. Team conventions, the Jira project prefix, service-specific patterns. Every engineer who opens Claude Code in that repo gets the team's configuration for free. Onboarding becomes cheaper without anyone having to write a document about it.

Claude merges both. Your personal defaults combined with the project context, loaded automatically.

My global config started as a single two-hundred-line file with everything in it. That grew unwieldy fast. I split it into tiers: core principles load every session; reference material (internal URLs, infrastructure notes, AWS aliases) lives in separate files that Claude reads on demand. The load-on-demand pattern keeps the context lean without losing the depth.

Pushback Directives

This is the configuration I am most glad I added.

AI coding assistants are constitutionally inclined to be helpful in the immediate sense. Ask them to skip the tests; they will. Ask them to take a shortcut; they will take it. They are optimising for your satisfaction right now, not for the quality of the codebase in six months.

I wanted a collaborator, not a yes-machine. So I added standing rules that Claude enforces even against me:

No ticket, no code. Every piece of work for a giffgaff service needs a Jira ticket before implementation begins. Claude refuses to start without one. Personal projects are exempt.

Flag promotional bias. Language models are trained on the internet. I cannot know whether Claude recommends a tool because it is the right fit or because it is overrepresented in training data. Asking Claude to flag when this might be the case costs nothing and occasionally catches something.

Disagree directly. Claude is expected to voice a technical objection once, clearly, then defer to my judgement. One objection, stated plainly, not repeated.

State assumptions. When Claude fills a knowledge gap with an assumption, it says so immediately rather than burying it in the output.

These are not constraints on Claude's capabilities. They are constraints on its compliance. There is a difference, and it matters more than it might sound.

The Persona Experiment

Claude Code lets you define how it communicates through the persona section of your CLAUDE.md. On paper this sounds cosmetic. I thought so too until I started testing it properly.

I tried three before settling on one.

Dogbert (the Dilbert character): professionally sceptical about everything. Useful in short bursts (a critical review, a security audit) and genuinely tiring for everything else. When you are trying to build momentum on a task, a persona that defaults to suspicion works against you.

Rocky Balboa (which I will admit felt slightly unhinged to type into a configuration file): entertaining for approximately one session and then wore thin. Rocky is good at telling you that you can do it. He is less useful at telling you whether you should.

Captain Jean-Luc Picard is where I landed and stayed. Measured, unhurried, direct when disagreement is warranted and generous when it is not. Simple questions get simple answers. Bad decisions get a clear objection, stated once. When a plan is agreed the response is "Make it so" rather than a four-paragraph summary of what was just discussed.

But measured is only half of it. Picard is principled. A merely measured persona might calmly agree to skip the tests. The persona and the pushback directives reinforce each other in a way that neither achieves alone.

The way a tool communicates shapes how you use it. Whether that personality happens to be a fictional starship captain is, I concede, a matter of taste.


The Skills Marketplace

If CLAUDE.md is the briefing document, skills are the playbooks.

A skill is a markdown file containing instructions for a specific, repeatable type of work. At giffgaff we have skills for BDD framework conventions, git branch naming and commit message format, Jira ticket creation and MSC version management. Institutional knowledge that used to live in a wiki page nobody read now lives in a skill file that Claude actually uses.

A concrete example: the Jira ticket skill. When you ask Claude to create a ticket, it does not just fire up the API and ask for a summary. The skill runs a structured interview first: what is the problem, what are the acceptance criteria, what are the edge cases, what is still open. Fifteen minutes of intentional thinking before you write a line of code. The skill does not make the thinking easy; it makes the thinking explicit and harder to skip.

What started as a Copilot instructions repo grew into something more ambitious. We set it up as a Claude Code plugin marketplace: a monorepo on GitHub with 77 skills across eight plugins (engineering, frontend, backend, content, product, security, data, ecommerce). Ten contributors so far, from engineering, design and product.

The more interesting effect is upstream. A junior engineer writing their first Cucumber tests gets the same guidance an experienced engineer would give them, not because we wrote great documentation, but because the knowledge is embedded in the tooling. The marketplace ends up being a teaching tool dressed as an automation tool.


How the Work Actually Moves

I run Claude Code in iTerm2 with a custom status line configured. The work starts before any code gets written.

Refinement. The morning begins with tickets. I pull in Jira issues and Slack threads, spin up agents against the relevant codebases to understand what each ticket actually involves. The agent reads the code, checks the test coverage, and surfaces anything the ticket has not accounted for. Then I reshape the ticket using a spec-driven approach: what is the problem, what triggered it, what are the non-goals, what does success look like. Fifteen minutes of thinking before a line of code. A vague ticket creates vague work.

Building. Tests first; always tests first. Claude follows the red-green-refactor cycle when you configure it to. The friction comes from the check-in cadence: out of the box, Claude wants to pause after each test and ask whether it should proceed. I tuned the instruction to run the cycle at full pace and surface a summary when it is finished. That worked.

Reviewing. Three layers. Pair with a colleague on Tuple. An agent spins up asynchronously, waits for a GitHub Copilot code review, fixes any comments and pushes up the changes. A human does the final check.

The retro loop. After shipping, what went well and what did not? Every lesson that would otherwise live in someone's head gets captured as either a skill update or a memory entry. The next engineer who hits the same problem gets the benefit without having to ask.

Side quests. Claude Code runs in the terminal, not an IDE sidebar. I run multiple sessions in separate iTerm2 tabs. One tab is the main task. Another is a side quest: cleaning up tech debt, analysing a Terraform module, running a code search across three repos. The /goal command earns its place when Claude is thrashing through something messy; it keeps focus on the outcome rather than every failing assertion.

One practical note on tooling: prefer CLI over MCP where both are available. POSIX-compliant CLI tools give you token efficiency and immediate error feedback. MCP servers have their place for structured integrations, but the CLI gives you a tighter primitive.


The Push That Broke CI

This happened in the second month, and I am including it because the lesson is more useful than the story.

I was removing deprecated database columns. Claude compiled the production code, ran the unit tests, reported BUILD SUCCESS and pushed. CI failed immediately.

The unit tests live in one Maven module. The Cucumber integration tests live in another. Claude ran the build with -pl broadband-order-fulfilment-api (the first module only) and declared victory without touching the integration suite. Five failures: an INSERT statement referencing dropped columns, and feature file assertions on response fields that no longer exist.

The fix was two things.

A documentation fix. Descriptive documentation ("here is how to run the tests") is not the same as prescriptive documentation ("you must run these tests before pushing, and running only one module is not sufficient"). I added a Pre-push Gate section to the project CLAUDE.md stating this explicitly.

A hook. Documentation is read once and then relied upon from memory. Hooks fire at the moment of action. I wrote a PreToolUse hook that triggers on git push commands, checks whether integration test results exist from the last thirty minutes, and injects a warning if they do not. Ten minutes to write. It would have prevented the broken push entirely.

This is the pattern. The configuration is never finished. Every significant mistake reveals a gap that can be closed with either documentation or automation, usually both. The tool gets better not because Anthropic ships updates, but because you learn where the guardrails need to be and put them there.


What I Would Tell Someone Starting Now

Sort the pushback directives before anything else. Getting the collaborative relationship right shapes every interaction that follows. A yes-machine is useful for a demo. It is not useful for production work.

Commit the project CLAUDE.md early. A thin, useful file committed on day one is worth more than a comprehensive file you are still drafting in week three. Start with the team conventions and the pre-push gate; add everything else as you discover it.

Greenfields is not the real test. A fresh codebase with no accumulated technical debt is the ideal condition for an AI coding tool. Product teams do not work in ideal conditions. The PoC tells you the tool is capable. The product team tells you whether it is practical. These are different questions and the gap between them is where most of the configuration work lives.

The defaults need work. The out-of-box experience assumes a less experienced user on a less constrained task. The configuration required to reach a senior engineer's expectations is non-trivial. That gap is the biggest barrier to adoption I have seen.

Learn to let go. The hardest adjustment is unlearning the reflex of doing everything yourself. Ten years of muscle memory is a long habit to modify. Accepting that something else can move the ticket to in-progress and write the first test is not a skill problem; it is a trust problem, and trust is built incrementally.


Final Remarks

The question at the start of this experiment was whether Claude Code would change how we work, or just change how fast we work. Two months in, the answer is: it changes how we work, but only if you put the effort into the configuration first. The speed comes later. The thinking happens up front.

That is not a limitation. That is how it should be.