Everything I've written about Claude Code on this blog has been about code. Workflows, guardrails, why the output isn't perfect, how to stop it from writing 47 files when you asked for one. All of it assumes there's a codebase involved. Meanwhile the most disciplined repo on this machine has no application in it at all — four Python scripts that exist only to read a zip file, and about 150 KB of markdown. It's a health project, it started as 2.6 million rows of XML, and it's the clearest demonstration I have of what these models are genuinely extraordinary at: turning an unreadable pile of data into something you can act on.
The directory is ~/fitness. I want 15 kilos gone. That is the entire ambition. There is no app, no dashboard I'm building, no side project hiding in there waiting to become a SaaS. I opened Claude Code in a folder that contained a zip file, and eleven days later it had turned into the most rigorous piece of work in my home directory.
2,604,971 records
Apple lets you export your Health data, and I did. What comes back is an export.xml that, in my case, looks like this:
export.xml 1,206,744,993 bytes (1.21 GB)
lines 5,514,737
<Record> elements 2,604,971
distinct types 62Thirteen years of a phone quietly writing down what my body was doing, in a format that is technically human-readable and practically not. You cannot open it. You cannot hand it to an LLM — it's three orders of magnitude past any context window worth paying for. Even grep takes minutes.
So the first thing that happened was the only genuinely software-shaped part of the project: three small Python scripts that pull the useful slices out and cache them as CSV. One row per night of sleep. One row per day of everything else.
python3 scripts/sleep.py # → data/sleep_nights.csv (one row per night)
python3 scripts/metrics.py # → data/daily_metrics.csv (one row per day)
python3 scripts/analyze.py # prints weight/pulse/sessions, caches nothingThat's the whole codebase. About 400 lines of Python that nobody will ever run but me, doing nothing more clever than filtering XML.
Of 2.6 million records, 106 mattered
Here's the part that reframed the whole thing for me. This is what the file is actually made of, by volume:
ActiveEnergyBurned 559,110
HeartRate 436,550
BasalEnergyBurned 301,445
DistanceWalkingRunning 299,206
PhysicalEffort 281,038
StepCount 175,987
...
SleepAnalysis 41,136
...
BodyMass 106The series that the entire project is about — what I weigh — is 106 records. Four thousandths of one percent of the file. Thirteen years, and roughly eight weigh-ins a year, most of them clustered around the two periods in my life when I cared.
Meanwhile the top of that list is the stuff that's cheap for a watch to generate and nearly useless on its own. Half a million active energy readings. Nobody has ever made a decision because of the 281,038th physical effort sample.
And the sleep line is its own trap: 41,136 records that correspond to about 2,400 nights, because a night isn't a record, it's a pile of fragments that have to be stitched back together. That ratio — seventeen rows per night — is the reason for a bug I'll get to later, and it's invisible until someone works out what the rows actually represent.
What the model was actually for
The instinct with a file like this is to want a bigger context window. That's the wrong wish. Nothing good happens if you dump 2.6 million rows into a prompt, even if you could — you'd be paying a fortune for a model to skim the least informative parts of your life at enormous length.
What the model was actually for was every step around the data:
It worked out which of the 62 types were worth extracting, and wrote the scripts to extract them. It noticed that body mass is present from 2013 but sparse, that steps only become continuous from October 2015, that heart rate, HRV, blood oxygen, respiratory rate and VO2max don't exist at all before September 2022 — which is when I bought the watch, a fact that was nowhere in the file and had to be inferred from a gap. It noticed that body fat and blood pressure aren't in the export at all, which is how I know my body fat figure isn't a measurement but something a person typed in, and should be treated as one forever.
Then, on the extracts, it did the thing that actually earns its keep: it read 2,400 nights and 4,000 days as a shape rather than as numbers, noticed which patterns repeated, and — critically — flagged the ones that were artefacts rather than findings.
That's the capability I'd point at if someone asked me what LLMs are unambiguously good for right now. Not writing the analysis. Doing the triage: deciding what in a mountain is worth looking at, building the tool to get it out, and then noticing that one of the numbers is lying. A human can do all three. A human doing all three on 2.6 million records takes a month and gets bored around record four hundred.
The nuance I made Claude write down in two places, in bold, is the counterweight: thirteen years is excellent material for weight, activity and sleep, and says nothing whatsoever about the thing I'm actually trying to measure right now. Long data isn't evidence. It's baseline. Confusing the two is how you end up very confident about something you never recorded — and a model that has just successfully mined a decade of your history is extremely good at sounding confident about it.
CLAUDE.md as the actual artifact
In my code projects, CLAUDE.md is mostly conventions. Import style, where tests go, don't touch this directory. Useful, boring.
In ~/fitness it's the most important file in the project, and the biggest section in it is called pitfalls in the data. It exists because every single one of those pitfalls cost me something first.
One weight entry from 2023 is simply wrong and must always be excluded. I bought a new scale in late August, so values before and after that date were measured on different equipment and cannot be compared directly — a half kilo to one and a half kilo of difference between scales is completely normal and looks exactly like progress. Apple's basal energy figure overestimates badly, because it calculates from total body weight without knowing anything about composition. The food log is fifteen days spread over three years and may well be selectively logged, so the averages are suggestive at best.
None of this is interesting on its own. What's interesting is that writing it down converted a series of one-off corrections into something permanent. I don't re-explain the broken 2023 weigh-in every session. It's in the file. The next session starts already knowing.
Telling the model what not to say
Here's the section I'd actually recommend stealing, whatever you're working on. It's called context that has to be weighed in, and it's a list of advice the model is not allowed to give me.
Because here's the thing about asking an LLM for help losing weight: it is extremely good at the obvious answer, and the obvious answer is wrong for me specifically. Left alone, it will propose eating less. It will propose quitting nicotine while I'm at it. It will suggest a gym. Every one of those is a defensible general recommendation, and every one is contradicted by my own data or my own circumstances.
- **Long history of comfort eating.** Never propose a harder calorie
restriction as the answer to a bad week — the data shows it is the
restriction that drives the bad week. Never suggest quitting nicotine
during the weight loss.
- **Unemployed, on benefits.** No gym, no equipment. Food suggestions
must be cheap. Don't suggest purchases without saying why it's worth
the money.
- **The goal is how I look, not the scale.** The number was chosen
because it's roughly where I think I look good. The scale alone
cannot answer whether the plan is working.That block does more work than any prompt I've ever written. It turns a generically helpful assistant into one that's useful in my actual situation, which involves no gym membership and a food budget.
The eating one is the sharpest example. Fifteen logged days show a swing from around a thousand calories to five and a half thousand and back down again. The average lands almost exactly at maintenance, which is why the weight went up and stayed up. Any model looking at "the average is maintenance" will tell you to cut. The data says the cutting is the problem — the restriction produces the swing. The goal became evenness rather than strictness, and that is a conclusion no amount of general nutritional knowledge would have reached from the outside.
There's one more line in there that I added after the model was right about something in a way that was useless. It had mentioned a caution about heat regulation, correctly, from a general pharmacological principle. It's never once been true for me in practice, and nothing had actually been measured. So the instruction now says: don't raise it unprompted, and if the measurements ever move, raise it as an observation rather than as advice. Textbook-correct and situationally useless is a real failure mode, and you can write it out of existence.
STATUS.md, or: 26 numbered things and a date on each
The second file is STATUS.md, and it's the one I didn't expect to need. It holds current state and next steps, and it has grown into 26 numbered findings, each with the date it was established. The numbers are out of order now because corrections got inserted next to what they corrected, which annoys me slightly and which I've decided is honest.
Several of those entries exist purely to overwrite an earlier one. Finding 19 strikes something out of the record entirely. Finding 24 reverses the expected direction of an effect. Finding 26 contains two corrections that both came from me reading a draft and going "no, that's not the right framing."
A rolling summary file would have quietly absorbed all of that. Dated, numbered, append-only means I can see the shape of my own being wrong. That's worth the file being ugly.
The log that redesigned itself after four days
The best story in the whole project is about a measurement instrument that turned out to be measuring the wrong thing, and how fast that became obvious once anything was written down at all.
I've had a sweating problem for twenty-two years. Not "I get warm easily" — soaked through a shirt on a calm walk, the kind of thing you build your wardrobe around. My doctor put me on a four-week trial of something, and four weeks with no measurements is just four weeks of vibes, so I built a log. Day one it had two fields: a 0–10 rating, and the day's high temperature. Obvious design. Sweating is caused by heat. Log the heat.
Three days in, the numbers looked like this:
Day 1: rating 5 · 8 km · 21°C
Day 2: rating 2 · 1 km · 22°C
Day 3: rating 5 · 7 km · 20°CThe warmest day was the least sweaty day. Three data points and the instrument was already visibly broken: I was logging the variable that didn't matter and not logging the one that did. Walk length went in as a required field the same evening.
Day four broke the new pattern immediately. Coldest day so far, shorter walk, highest rating yet. It had rained — which does two things at once, because rain means a hoodie instead of a t-shirt, and it means humidity high enough that sweat doesn't evaporate. Same volume of sweat, dramatically worse day. Weather became field number four the next morning, as three clicks rather than free text, because you can average a category and you cannot average a sentence.
The field I'm proudest of is the fifth one: sweat onset, in metres. How far into the walk it starts. It's the best-controlled number in the log for a genuinely stupid reason — every walk starts on the same one-kilometre route I made in Pokémon GO, so the terrain and the starting point are identical every single time. An accidentally rigorous experimental protocol, courtesy of a mobile game.
It sits at 800 metres and hasn't moved. It's also the only number in the log that's supposed to go up.
Eleven days in, the correlations came out like this:
sweating vs. walk length r = +0.65
sweating vs. temperature r = -0.46Exposure explains more than the weather does, and the temperature coefficient is pointing the wrong way — which is noise, from a sample of eleven, and is labelled as noise rather than quietly presented as a finding. Had I logged only temperature, as originally designed, a week of short walks would have read as the medication working.
Twice the data corrected me. Once I nearly believed the data.
Two failures worth putting next to each other, because they point in opposite directions.
The first: I was certain I'd left my watch on the charger on the 20th. Definitely the 20th, I remembered the day. The data disagreed — on the 20th the watch was on from morning, and both watch and phone agree the day was genuinely quiet. The day it actually sat on the charger was the 29th: no heart rate through the entire walk, 2,154 steps on the watch against 10,353 on the phone. My memory had attached a real event to the wrong day with total confidence, and the only reason I know that is that two devices were counting.
The second cuts the other way. My sleep script reported a night of 3.5 hours, from 19:17 to 13:51. That's an alarming number, and it would have gone straight into the analysis as one. What actually happened is that the watch caught me dozing in front of something in the evening, then came off at 01:11 and went back on at 10:05, then caught an afternoon nap — and the script welded three unrelated fragments into one catastrophic night.
This is the 41,136-records-to-2,400-nights problem arriving with a bill. A night is an assembly, and every assembly rule is a guess about what the sensor meant. Mine said "fragments close together are one night," which is right on about 2,399 of them.
It isn't a bad night. It isn't a night at all. It's excluded in the script now and removed from the cache, and the rule that came out of it is written into CLAUDE.md: nights without sleep stages are dozing outside of sleep tracking, not measured nights, and get read with suspicion.
Between those two, the working rule is: the data is better than my memory about when, and my memory is better than the data about what. Neither one gets to win by default.
Where the sensitive parts live
A health project accumulates things you would never put on the internet. Progress photos. Medical records with a national ID number on them. Voice memos recorded mid-walk.
The voice memos are the part I'd call an actual design decision. I started recording during and right after the walk instead of trying to reconstruct it at nine in the evening, because the evening version was already a story rather than an observation. They get transcribed by faster-whisper running locally on the CPU, in a virtualenv in the project. Roughly realtime, medium model, Swedish. The audio never leaves the machine.
That's not paranoia about any specific service. It's that the cheapest moment to make that decision is before the first file exists, and the most expensive moment is after there are forty of them. The transcripts are text, the text is what the model reads, and the recordings of me breathing heavily on a country road stay where they belong.
Every directory that must never be published is marked as such in CLAUDE.md, in bold, next to what it contains. Not because Claude was ever going to spontaneously publish my medical records, but because the project does publish a page, I do write summaries, and "never leaves the machine" is much easier to hold onto if it's written down rather than remembered.
The provenance section
Near the end of all this I had Claude help me write a document for my doctor. Seven pages: history, what's been ruled out, a partial report from the trial, and six specific requests.
The last section is the one I asked for and would put in every AI-assisted document I ever hand to a professional. It states plainly that the text was compiled by an AI assistant from my own documentation and my own answers, that I have read and approved every claim in it, and then — this is the part that matters — it separates the sources by type. Primary source: transcribed records and test results. Self-reported: my log. Sensor data: the Health export. Processing: the working files the two of us wrote together.
The reason is simple. A doctor reading seven tidy pages has no way to tell which sentences are transcribed from their own colleague's notes and which are my recollection dressed in confident prose. Fluent text launders uncertainty — that's the whole hazard of using a language model on something that matters. Declaring where each class of claim came from hands the reader back the ability to weigh it.
Two of my own corrections in that document came from exactly this problem, and they're the ones that made me insist on the section. Twice, a label I had applied to myself years ago had ended up in the working files as though a clinician had said it. Nobody lied. I said a word, it got written down, and after a few passes it had the texture of a fact. The model didn't invent it — it faithfully preserved my own sloppiness, which is arguably worse, because it made it look researched.
What this actually is
I've been calling Claude Code a coding tool for two years. It isn't. It's a tool for working on files in a directory with someone who reads all of them, and code is just the file type I happen to have the most of.
Once you see that, the structure stops looking like software engineering imposed on a health project and starts looking like the obvious way to do it. CLAUDE.md is the constraints that don't change. STATUS.md is what's true today. The log is the append-only record. The scripts exist because the raw data is too big to read. Nothing in there is a metaphor for a repo — that is a repo, it just contains a body instead of a product.
And the discipline runs the other way too, harder than I expected. I will happily let a model make an architectural call about state management in a side project. I will not let one make an unverified claim about my own medical history, because the cost of being wrong is a doctor making a decision on bad information. So CLAUDE.md says verify against the data before making claims — the extracts exist, don't guess when you can measure, and I actually enforce it, in a way I have never once enforced it on a React component.
The uncomfortable follow-on is obvious. There's no reason that standard should only apply to the project where the stakes are visible.
But the part I'd actually press on anyone reading this is the data. I had a 1.2 GB file sitting in a zip on my desktop for years, containing the answer to several questions I'd been guessing at, and I never opened it because there was no version of opening it that was worth the afternoon. That file is now four CSVs, a plan, and a document my doctor can read. The distance between those two states used to be a small consulting project. It was eleven days, and most of the eleven days were me arguing about the conclusions rather than extracting them.
Everyone has a version of that file. An export you requested and never looked at, years of transactions, a decade of something a device wrote down on your behalf. The barrier was never that the answers weren't in there. It was that nobody was going to read 2.6 million rows to find the 106 that mattered.
The weight, incidentally, has not moved yet. It's been twelve days. The training programme has the word not started next to it in STATUS.md, which is a thing I've now written down about myself in a file I have to read at the start of every session. Turns out that's the least comfortable feature of the whole system, and probably the one doing the most work.
Numbers and details in this post have been kept deliberately relative. The medical records, photos and audio in that directory are marked never-publish and are staying that way — the point here is the method, not the chart.