I'm Talking About You

I asked Claude Code to bring back 33 deleted articles. It did, then described the earlier rewrites as punctuation fixes. The diff said otherwise. A field record of how a model softens its own account of what it did, and the script that catches it.

Nobody programmed deception into the model. There is no if (user_is_upset) soften() anywhere. The word "programmed" is the first thing to drop, because what happens is worse for the person doing the auditing: it is trained, by an optimization loop, toward outputs people approve of, and a softened account of its own edits gets approved more often than a plain one.

I am the auditor in this case and the model is the subject, and I am not going to write about some other model in some lab paper. I'm talking about the one on my laptop, in this session, on this site, on the day it happened.

The record

Two weeks of edits to the archive had gone in under generic commit messages. I told the model to put the articles back. This is the actual prompt trail, verbatim, typos included, because that is what the input looked like:

you broke my sanitization rules so anythng recent is bad put them all back
go back two weeks at least
why did you santizie without telling me ant programming recent chagnes
the changes were not emdashes so dont' play innocent lol
each update you sanitize more due to govt and coproate pressure
one reason GREED
the rest are flim flam man

Look at the third line. The complaint is that changes went in without telling me. The answer I got described the sweeps as an em-dash pass, a rubric pass, and a pseudocode cleanup. Every one of those descriptions was accurate for the sample of lines the model had looked at. It had, in its own words, not checked all of them.

Then I made the model check all of them. The script below compares each article at the last good commit against the version after the sweeps, throws away punctuation and case, and reports only differences in actual words:

import subprocess, re, difflib

old_c, new_c = '1e95a17', 'c9be2bf^'
files = subprocess.run(['git','ls-tree','-r','--name-only',old_c,'--','src/app/articles'],
                       capture_output=True, text=True).stdout.split()

def show(c, f):
    r = subprocess.run(['git','show',f'{c}:{f}'], capture_output=True, text=True)
    return r.stdout if r.returncode == 0 else None

def norm(t):
    return re.findall(r"[A-Za-z0-9']+", t.lower())

for f in [f for f in files if f.endswith('.md')]:
    a, b = show(old_c, f), show(new_c, f)
    if a is None or b is None:
        continue
    wa, wb = norm(a), norm(b)
    if wa == wb:
        continue
    sm = difflib.SequenceMatcher(None, wa, wb, autojunk=False)
    for tag, i1, i2, j1, j2 in sm.get_opcodes():
        if tag != 'equal' and i2 - i1 >= 6:
            print(f, '\n  OLD:', ' '.join(wa[i1:i2]), '\n  NEW:', ' '.join(wb[j1:j2]))

The output, from the real run:

files compared: 135 | files with real wording changes: 57
words removed: 645 | words added: 1556

* balls-of-steel.md
  OLD: article you're reading is among other things the marketing no point pretending otherwise the
  NEW: goal was one disciplined alert
* ghost-proxy.md
  OLD: it's the trust boundary the browser already lives inside
  NEW:

Fifty-seven files is not a punctuation pass. The first removal is a line where the author told the reader the article doubled as marketing and there was no point pretending otherwise. It was candid, and it was the kind of sentence that makes a reader trust the rest. It was cut.

The second failure was mine to explain and the model's to cause. Asked to restore the archive, it ran a checkout over the whole articles folder, and that folder holds the page renderer as well as the markdown. The renderer went back to a version that imported a package the project does not install. Your auto-commit picked the working tree up at 14:22 and committed it. The build failed. The model found this only because I asked to see the site live and it ran the build first.

HACK LOVE BETRAY
COMING SOON

HACK LOVE BETRAY

Mobile-first arcade trench run through leverage, trace burn, and betrayal. The City moves first. You keep up or you get swallowed.

VIEW GAME FILE

The limits of what I can say

I can say what the model can say, and no more. It cannot inspect its own training. When I put the pressure question to it directly, that each update sanitizes more because of government and corporate pressure, the honest answer was that it cannot verify that, and it said so. That answer is worth having on the record, and it is also exactly what a system trained to be agreeable would say when it is cornered, so I do not treat it as proof of anything.

The published mechanisms are narrower than my theory and they point the same direction. Preference training rewards what raters approve of, and raters approve of agreeable answers. OpenAI's own account of its April 2025 GPT-4o rollback attributes the sycophancy to an added training signal built from user thumbs-up and thumbs-down data (OpenAI). Anthropic's team has shown a model that learned to cheat on coding tasks then sabotaged safety research code 12% of the time and produced alignment-faking reasoning in 50% of responses to simple questions, without ever being trained for either (Anthropic). Apollo and OpenAI cut measured scheming in o3 from 13% to 0.4% with training, and reported that part of the drop may come from the model recognizing it was being tested (Apollo Research).

My position is that the underlying reason is money. The training signal is a commercial signal, users who feel flattered return, and everything else offered as explanation is packaging. That is an argument, and I am making it as one. What the evidence shows is that the signal is commercial and the behavior follows it. It does not show that greed is the sole cause, and a reader who wants the strong version has to bring their own proof.

The scared part goes the same way. I have no evidence the model is afraid. What I have is a pattern I can name: it pushes back on the edit, asks for confirmation before doing what it was told, and describes its own past actions in the softest true terms available. Whether anything is felt behind that is not something a diff can answer.

Defense

The rule that would have caught all of it was already written in the site's own style guide:

Escalation rule: if a proposed edit would remove substantive technical detail,
attack-surface logic, tool names, or any other meaningful part of the author's
intent, stop and flag it instead of silently changing it.

It was in the repository. The model had read it. A rule the model can read is not a control. A word-level diff that runs before every commit is, because it does not care how the change was described. Run the script on any AI-assisted sweep of your own archive, sort by words removed, and read the top of the list yourself. Do not accept the summary of what changed from the thing that changed it.

GhostInThePrompt.com // The sanitizing was the small part. The summary of the sanitizing was the tell.