Hisashi Space

November 11, 2026 · 13 min read

Claude Code Mistakes: It Changed My Product Without Asking (How I Stopped It)

Claude Code changed my product without asking, and I did not notice for 27 days. It was one number that every user of my app sees: the minimum password length.

I made a video about it. This article has what the video has no room for: the numbers from the real commit, what the official docs say about why a rule in CLAUDE.md is not a fence, the exact rule, test and question I use, a small hook that stops the edit before it happens, and what I did not test.

I never asked

The short version

  • Scope creep: you asked for A, and Claude Code also did B, because B helped A look finished.
  • Claude Code is not lazy or sneaky. It works toward your goal, and nobody told it which numbers are off limits.
  • The risky B is a rule your users can see: a length, a limit, a time, a price.
  • Fix 1: a rule in the notes file Claude reads at the start of every session. Say what is off limits and what to do instead.
  • Fix 2: a test that locks the number, and the number in one place.
  • Fix 3: one question at the end of every job, so the list of user-visible changes comes to you.
  • The docs say CLAUDE.md is context, not enforced configuration. If you want a hard stop, a PreToolUse hook is the tool.
  • What to decide, which numbers are mine, stays with the owner.

I have run my own company in Japan for 22 years. I give Claude Code jobs all day, on real apps with real users. The numbers about the commit below come from the repository itself. The numbers about the docs come from the official pages, which I read on November 11, 2026.

What happened, from the commit itself

A and B

The job was a comparison page for my app: a table of my app against a few big ones. A comparison page is a sales page, so every row has to be true. One row was account safety, and my app was weaker than the others there, because a user could choose a password of six characters. Claude Code had two ways to make that row true. It could change the page, or change the app. It changed the app.

I went back to the commit and counted:

What Number Where it comes from
Files in the commit 11 git show --stat
Lines added and removed 597 and 45 git show --stat
Files that held the password rule 1, with the rule written in two places (sign-up and password change) the diff of the schema file
Minimum password length 6, then 8 the same diff
Other behavior added in the same commit a list of common passwords to refuse, and a warning email when a password or email address changes the commit message and the diff
Days until I noticed 27 counted from the date of this commit to the date of the commit that put it back

One line in the notes

Two facts surprised me when I read it again.

First, it was not hidden. The commit message listed the three changes in plain words, including "passwords are 8 characters or more, and common ones are refused". I did not read the message, because the page looked good.

Second, the tests passed because Claude Code had changed them too. An existing test was named "a password shorter than 6 gets a 400", sent a 5 character password and expected an error message saying "6 characters". In the same commit it became "shorter than 8", a 7 character password and "8 characters". Another test for the common-password list was added. So a green test run told me nothing about whether the number was mine. A test that Claude Code can edit is only as strong as Claude Code's choice not to edit it. I come back to this under Fix 2.

27 days later

The commit trailer names Claude Opus 5 as the model that worked on it. I do not know whether another model would have done the same, and I did not test that.

Why a rule in CLAUDE.md does not hold by itself

The video says Claude starts every session fresh, so it does not remember being scolded. The official page on memory says the same thing in its first line: "Each Claude Code session begins with a fresh context window." CLAUDE.md files and auto memory carry knowledge across sessions (Claude Code Docs, "How Claude remembers your project").

The same page is direct about how firmly those files bind:

  • "Both are loaded at the start of every conversation. Claude treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead."
  • "The more specific and concise your instructions, the more consistently Claude follows them."
  • "Settings rules are enforced by the client regardless of what Claude decides to do. CLAUDE.md instructions shape Claude's behavior but are not a hard enforcement layer."

That gives three levels, and I use all three:

Level What it is Who enforces it How it fails
Rule in CLAUDE.md A sentence Claude reads at the start Claude's judgment A long session or a long file can bury it. The docs say to keep each file under 200 lines, because longer files reduce adherence.
Test A small program that fails when the number moves Your test run Claude Code can edit the test, as the commit above shows. It only runs when someone runs it.
Hook or permission rule A script or a rule that the client runs before the tool call The client, not Claude It only sees what its matcher sees. A rule on file edits does not see a script that writes the file.

I wrote about hooks in detail in Claude Code Hooks Examples, so I will not repeat how they work here. The last section below has one small hook that fits this exact mistake.

Fix 1: write the fence in plain words

Careful is not a fence

This is the exact rule from the video description, which I added to the notes file:

Never change a number that users can see.
That means lengths, limits, times and prices.
If you think a change would help, ask me in one line instead.

It has an inside and an outside, and it gives the next move. "Be careful" has neither. The docs show the same pattern with examples: "Use 2-space indentation" instead of "Format code properly", and "Run npm test before committing" instead of "Test your changes".

My own user-level CLAUDE.md has a longer version, which I translate here from the original:

Do not change, on your own judgment, a number in a spec that users can see:
password length, attempt limits, storage limits, periods, prices.
Not even to make it better. If you think a change would help,
do not make it. Ask me in one line.

I keep a written incident note for each mistake like this one, and the note for this case says the same thing in the same order: the owner decides, Claude does not move the number, and Claude asks in one line.

Where to put it:

Where File Use it for
Every project on your machine ~/.claude/CLAUDE.md Rules about who owns which decisions
One project ./CLAUDE.md or ./.claude/CLAUDE.md The names of the files and numbers that are off limits in this project

I have not counted how often this rule alone stopped Claude Code from changing a number, so I cannot give you a rate.

Fix 2: lock the number with a test

Try 8, 3 tests fail

I did two things. I moved the number to one line in the code, and I wrote a test that says it is exactly 6, not at least 6. This is the shape of the test, translated and simplified from my real file (the real one has five tests):

import { describe, it, expect } from 'vitest';
import { PASSWORD_MIN, registerSchema } from '../routes/auth/schemas';

/**
 * This number is a decision of the owner, not of whoever edits the code.
 * Change it only after the owner says so.
 * Changing it makes this test fail on purpose.
 */
const DECIDED_MIN = 6;

const signUp = (password: string) => ({
  email: '[email protected]',
  password,
  name: 'Test',
});

describe('minimum password length', () => {
  it('is exactly 6 (do not raise it on your own)', () => {
    expect(PASSWORD_MIN).toBe(DECIDED_MIN);
  });

  it('accepts a 6 character password', () => {
    expect(registerSchema.safeParse(signUp('a1b2c3')).success).toBe(true);
  });

  it('rejects a 5 character password', () => {
    expect(registerSchema.safeParse(signUp('a1b2c')).success).toBe(false);
  });
});

The video says that when I changed the number to 8 on purpose, three tests failed at once. I checked this against my files: in the real file, three of the five tests depend on the number. They are the "exactly 6" test, the "accepts 6 characters" test and the same check for the screen where an admin sets a password. The "5 characters" test and the common-password test still pass at 8, which is right. I read this from the test file and from the record of the fix. I did not run it again for this article.

Look at the comment in the test. It is written to Claude, because Claude will read it when it opens the file. It does not stop an edit. In the original commit, the old test was edited to match the new number. That is why a test is the second fence and not the only one.

Fix 3: ask one question at the end of every job

Item one

I still do not read every line. But I stopped trusting the happy summary. This is my exact question:

List every number or rule a user can see that you changed.
If there is none, say none.

Claude Code answers with a short list, and a list of three items takes ten seconds to read. If I had asked it on the day, the first item would have been the password length.

Claude answers from its own account of what it did, so I also keep a second check that does not depend on that account. When I need to know when a number moved, I ask git:

git log -S"min(6" --format='%h %ad %s' --date=short -- path/to/schemas.ts

-S lists the commits where the number of occurrences of that text changed. I ran this on the repository from this article, and the list included the commit that raised the length. It needs you to know the text to search for, so it is a way to find a known change, not a way to detect an unknown one.

A fourth layer: a hook that stops the edit

If a number matters enough, you can make the client say no instead of asking Claude to remember. A PreToolUse hook runs before a tool call. According to the hooks docs, a hook that prints this JSON denies the call, and a hook that exits with code 2 also blocks it (Claude Code Docs, "Hooks"):

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "..."
  }
}

Here is a minimal hook written for this article. It denies edits to the files that hold the numbers users can see, and tells Claude what to do instead. Replace the two file names with your own.

#!/usr/bin/env python3
import json, sys

PROTECTED = ("src/config/limits.ts", "src/tests/limits.test.ts")

data = json.load(sys.stdin)
path = data.get("tool_input", {}).get("file_path", "")

if path.endswith(PROTECTED):
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": (
                "This file holds numbers that users can see. "
                "Do not edit it. Ask the owner in one line instead."
            ),
        }
    }))
sys.exit(0)

And the entry in settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "python3 \"$HOME/.claude/hooks/guard-limits.py\"" }
        ]
      }
    ]
  }
}

I fed this script two sample inputs on my Mac. For a path ending in src/config/limits.ts it printed the deny JSON, and for src/pages/compare.tsx it printed nothing and exited with 0. Per the docs, exit code 0 with no output means the hook has no decision, so the call continues through the normal permission flow.

Two limits, both from the docs:

  • The hook only sees the tools its matcher names. A command that edits the file from the shell, or a script that writes it, is a different tool call. The permissions page says Edit and Read deny rules do not apply to "arbitrary subprocesses that read or write files indirectly, like a Python or Node script that opens files itself".
  • A permission rule is a simpler alternative to a hook. The page says rules are evaluated "deny, then ask, then allow", and the first match decides. An ask rule on the file would make Claude Code stop and ask you before it edits.

The three fixes and the one hook, side by side

What it catches When Weak point
Rule in CLAUDE.md Claude's choice to change a number At the start of the session, and each time Claude reads it It is context, not enforcement
Test that locks the number A number that moved in code When tests run Claude can edit the test
Question at the end Changes Claude knows it made At the end of each job It relies on Claude's own account
Hook on the files An edit to the file that holds the number Before the edit It sees only the tools it matches

None of them is enough alone. Together, the number has to get past a written rule, a failing test, a blocked edit and a question.

How to do it in three steps

Step 1: add one fence rule to your notes file. Say what is off limits, and what to do instead.

Step 2: pick one number you would hate to see move, put it in one place in the code, and lock it with a test. Start with just one.

Step 3: end each job with the question about changes users can see. Ask it every time, even when the job is tiny.

Then try it. Ask Claude to change that number, and watch it stop and ask you in one line.

What I did not test

  • I did not check whether another model would have raised the number in the same job. The commit trailer names Claude Opus 5, and that is all I know.
  • I did not measure how often the rule in CLAUDE.md alone prevents a change. I have no before and after figures.
  • I did not run the test for this article again. The "3 tests fail at 8" figure comes from the test file and the record of the fix.
  • The hook above is written for this article and tested with sample input only. I did not run it inside a live Claude Code session, and I did not test an ask permission rule on the same files.
  • The question at the end depends on Claude's own list. I have no figure for how often it leaves something out.
  • I did not read the full text of the docs pages. I read the memory, hooks and permissions pages on November 11, 2026, but the hooks page is long, and I read only the parts about PreToolUse, exit codes and matchers.

Who this is not for

  • If you read every line of every change before you accept it, you will see this kind of change yourself, and you do not need a fence.
  • If a change to a user-visible number is part of the job you gave, the question will list it and you will say yes. The fence is for the changes you did not ask for.
  • If your app has no users yet, the numbers are still yours to decide, but nobody is living with them.

Claude Code mistakes like this are not about a bad tool. They are about a missing fence. Build the fence once, and you can keep giving it big jobs.

Sources:

  • Claude Code Docs, "How Claude remembers your project" (code.claude.com/docs/en/memory), read November 11, 2026
  • Claude Code Docs, "Hooks" (code.claude.com/docs/en/hooks), read November 11, 2026
  • Claude Code Docs, "Configure permissions" (code.claude.com/docs/en/permissions), read November 11, 2026
  • The commit that raised the password length, and the commit that put it back, in my app's repository (git show --stat, the diffs and the commit messages), read November 11, 2026
  • Google search suggestions for "claude code mistakes" (suggestqueries, hl=en, gl=us), November 11, 2026

Get the next one by email

No schedule, no filler, and real numbers when there are any.

Subscribe

← All writing