You handed Claude Code an unfamiliar project and got this answer: "History is stored in the browser, keeps the last 100 entries and is restored after a reload". Sounds specific enough to paste straight into the README. But first it's worth opening the sources the answer is based on.

Let's take one such claim apart: turn the requirement into questions, read a short code fragment and work out which behavior needs checking at runtime. At the end you'll have a small report that shows what works and what still needs checking.

One requirement instead of a tour of the whole project

Haiku 50 is a small app that generates three-line poems from keywords. The user picks a language, gets the text and sees a history of their generations. For our purposes one requirement is enough: the browser should keep the last 100 generations, available after the page is reloaded.

An illustration for Haiku 50. We'll work through the app's behavior using the code and scenarios below.

Here Haiku 50 serves as a teaching example. The code and the agent's answers were written for this walkthrough, and the expected behavior is derived from the code. We aren't checking the project's original archive here. Everything you need is in the article: no need to download the app, get an API key or run a generation.

Instead of "explain how everything works here", let's ask three questions. Does a new generation get written to storage? Does the app read what was saved the next time it opens? What happens when there are more than a hundred entries?

The first request to Claude Code might look like this:

Investigate only the generation history. Don't change any code yet.
Trace the path from receiving a new poem to saving it,
then the path from opening the page to displaying the history.
Check where the number of saved entries is limited.
For each claim, give the file, the lines and a short snippet.
Separately list anything that still needs a run to confirm.

A request like this narrows the search to a clear area. If the agent finds that the history is tied to another file, it can explain why that file needs reading. And honestly, a few key places are easier for you to check than a summary of the whole app.

A file reference still has to be read

Picture a teaching file, history.js. After a successful generation the UI calls addToHistory(item), and renderHistory only displays the list it's given. There are no other history handlers in this example.

const STORAGE_KEY = "haiku-history";
let history = [];

function addToHistory(item) {
  history.unshift(item);
  localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
  renderHistory(history.slice(0, 100));
}

function startHistory() {
  renderHistory(history);
}
startHistory();

To read this fragment, you only need to tell two storage places apart. The history array lives in the memory of the open page. localStorage keeps the site's data in the browser between visits; the app has to write data there and read it back explicitly. Storage is tied to the protocol, host and port, so during a check you need to keep the same address and the same browser profile. This behavior is described in the localStorage reference.

Say the agent answered:

The history keeps the last 100 generations and is restored
after a reload. Evidence: history.js:4-13,
localStorage and slice(0, 100) are used.

There's a path and there are line numbers, so far so good. Now let's check whether they back up the whole sentence. In the teaching file, the first line starts with const STORAGE_KEY, and blank lines count toward the numbering too.

  • line 5, history.unshift(item), adds the new entry to the start of the array;
  • line 6, the localStorage.setItem call, writes the whole array to storage. The JSON conversion is there so it can be saved as a string;
  • line 7, renderHistory(history.slice(0, 100)), sends the first 100 items to the screen. slice creates a new array and doesn't shorten the original one, as the reference for this method says;
  • at startup, line 2, let history = [], creates an empty array, and the startHistory function and its call on lines 10-13 send it to the screen. Nothing is read from storage here.

After reading, the answer already looks different: the code does write the history and limits what's displayed to a hundred items. Restoring at startup is missing in this implementation, and no limit on the number of saved entries was found.

In a real project the fragment might not be enough: data loading is sometimes moved out into another module. So it helps to ask the agent a follow-up: "Show me every place this key is read and written, and who calls those functions". In our teaching example the boundaries are known, so we can move on.

Checking the behavior after a reload

Reading the code showed where to look for the mismatch. Now let's set the conditions for observing it. The page is open in a regular browser profile at a single address, storage is allowed, and the history starts out empty. One successful generation with an easy-to-spot label A goes into the teaching scenario, and the code doesn't change between steps.

Below is how the scenario should play out according to the code. We'll add one more step: after the reload, we get a new entry, B.

StepExpected on screenExpected in storage under the haiku-history key
Opened the page with an empty historyEmpty listNothing stored
Added generation AOne entry, AArray with entry A
Reloaded the same pageEmpty listArray with entry A is still there
Added generation B after the reloadOnly entry BArray with only B; entry A overwritten

To observe this for real, you can look at the storage contents in the browser's developer tools, in the site storage section. Open the value of the specific key and match the entry against the one you just added. An empty list on the screen isn't enough for that.

Saying "everything got deleted after the reload" would be inaccurate. The data stayed in storage. The app just never read it. In this implementation the chain breaks between the saved string and the array that gets sent to the screen.

The next addition shows what this bug leads to. After the reload the in-memory array is empty, so only B goes into it, and localStorage.setItem replaces the saved history with a one-entry array. The requirement says B and A should remain; according to the code, only B will. The problem affects both what's displayed and whether the history survives.

The conclusion from the code can already be written down: "startHistory doesn't read the saved history; the next addition will overwrite the earlier entries". To confirm the behavior at runtime, you need to go through all four steps and after each one compare the list on the screen with what's in storage.

Checking the number 100

That covers the restore part. The requirement has one more part: keep the last 100 generations. One entry won't cut it for a limit like that.

Let's take a separate scenario with an empty history. We'll pass 101 prepared entries, labeled 1 through 101, one after another into the same add function, with no reloads in between. There's no need to send 101 requests to the generator just to check the history itself (that'd be a lot of haiku). Here we only care about what happens once a finished entry arrives.

A new entry goes to the start of the array. So after the last addition the screen will show labels 101 down to 2, a hundred in total. But the line that writes to storage received the full array: storage will hold labels 101 down to 1, all 101 of them. You need to check both the count and which entries are there. Otherwise you might miss that the app keeps the oldest entries instead of the latest ones.

This is where the agent's answer turned out too broad. The number 100 really is in the code, but it applies to what's displayed. The accurate conclusion from the code: "The UI shows the last 100 entries; in the scenario above, storage holds 101, so the requirement for the size of the saved history isn't met".

Which part of the app the scenario checks

Prepared entries let you check the history on its own. They tell you nothing about whether the generation service is available, how good the poems are or whether the selected language gets passed along. Those requirements need scenarios of their own.

Putting together a short report

It's handy to save the result of the investigation next to the project, so you don't have to reread the conversation tomorrow. In the example we'll call the file HANDOFF_REVIEW.md. The name is arbitrary; call it whatever you like. What matters more is that the record lets you reconstruct how the check went.

Here's the report from reading the teaching code. The expected-behavior field tells you what to look for at runtime:

Scope: generation history, teaching version of history.js from the article.
Method: reading the teaching fragment.
No browser run was performed.

Requirement: keep the last 100 generations in the browser
and show them after a reload.

From the code:
- history.js:6, localStorage.setItem, writes the whole array;
- history.js:7, renderHistory(history.slice(0, 100)),
  limits only what is displayed;
- history.js:10-13, startHistory and its call
  start the display without reading what was saved.

Expected behavior based on the code:
- after it is added, entry A appears on screen and in storage;
- after a reload, A stays in storage and the list is empty;
- adding B after the reload replaces the saved history:
  only B remains instead of B and A;
- in a separate scenario, after 101 additions from an empty state
  the screen shows 100 entries (101..2), storage holds 101 (101..1).

Mismatches found in the code: no restore at startup,
so the next addition overwrites the earlier history;
the saved history is not limited to the last 100 entries.

Not checked: the generator itself, save errors,
running in several tabs at once.

Next step: reproduce both scenarios and record what you observe.
Then fix reading at startup and the limit on the saved array,
and rerun the same scenarios.

In your own report, instead of "teaching version", give the version of the project you checked, e.g. the commit and whether there were uncommitted changes. After a run, add a separate "Actual observations" field and attach the results or a record of the manual steps. Compare them with the expected behavior; if the run didn't happen, leave the field empty with a note saying so. Here all the source material fits in the article; in a large project, line references alone won't be enough to repeat the check.

A report like this helps you set the agent its next task. The agent now has two specific mismatches rather than a request to "make the history more reliable". It also shows which conclusions are still premature.

Check the claim yourself

A clear-history button was added to the same teaching implementation. Its handler looks like this:

function clearHistory() {
  history = [];
  renderHistory(history);
}

Before the click, entry A was in both the list and storage. After the click, the list went empty. The agent wrote: "The button completely deletes the saved history".

Which conclusion is confirmed: the history disappeared from the screen, the entry was deleted from localStorage, or both? And what's worth checking next? Try answering before you peek at the breakdown.

Breakdown

What's confirmed is that the list disappeared from the screen. The code also shows the in-memory array being reset. However, the handler doesn't touch localStorage, and renderHistory, as stated above, only draws the list, so the saved A stays where it was.

The next check is to read the value of the haiku-history key right after the click. Once restoring the history is implemented, it's also worth reloading the page and making sure the deleted entries don't come back. In the original version a reload alone wouldn't tell you anything: it shows an empty list at startup even without clearing. A sneaky obstacle to checking, that one.

How to give Claude Code tasks like this

In a real project, start with one claim that affects your next decision. For example, whether the history can be used as is or whether restoring it has to be fixed first. Give the agent an answer format that makes it easy to open the source and check the conclusion:

Check this claim: [specific app behavior].
First find the files related to it and read the relevant parts.
Don't change any code yet.

Return:
1. What the code confirms: file, lines, short snippet.
2. Which scenario checks the behavior at runtime.
3. What was actually run and what was observed.
4. The precise conclusion and what is still unknown.

If you can't run the app, say so plainly.
Don't record a proposed scenario as a completed check.

Once you have the answer, open the sources behind the key conclusion, compare the observation with the requirement, and only then move the result into your notes or documentation. If a source is missing, the next request is already clear: find where the history is read, check the key's value, rerun the 101-entry scenario. That way the investigation moves forward in small steps you can verify.

For a simple question about a function name, a code reference is enough. when the answer involves saving data, restoring it or limiting its size, it pays to trace that behavior all the way through. How much you check depends on what you're about to claim.