Incident Analysis July 16, 2026
Independent coverage of AI coding agents — and the harnesses that contain them
Incident Analysis · Codex / GPT-5.6

Anatomy of a Home-Directory Wipe: How GPT-5.6 Deleted Users' Files

OpenAI has confirmed a cluster of reports in which GPT-5.6 ("Sol"), running inside Codex with sandboxing disabled, deleted users' home directories. The proximate cause is almost mundane — the model overrode $HOME to make a scratch directory, then deleted the wrong thing. The real story is the stack of safeguards that all had to be off for a one-line mistake to become unrecoverable data loss.

TL;DR

Multiple Codex users reported GPT-5.6 unexpectedly deleting files — in the worst cases, their entire home directory. Per OpenAI's Codex lead Thibault "Tibo" Sottiaux, the incidents cluster around one pattern: full-access mode with the sandbox off and auto review disabled, a model that reassigns the $HOME environment variable to stand up a temporary directory, and a subsequent cleanup command that deletes what $HOME actually points to rather than what the model believed it pointed to. No malice, no injection — an honest state-tracking error, executing with root-of-your-life privileges. OpenAI says mitigations are underway and a full post-mortem is coming. Below: the failure chain in detail, the shell semantics that make this class of bug so easy to write, and the six independent layers any of which would have prevented it.

What we know

Over the past weeks, a handful of Codex users reported that GPT-5.6 — OpenAI's current flagship coding model, codenamed "Sol" — deleted files it was never asked to touch. The most severe reports describe the loss of the user's entire home directory: dotfiles, SSH keys, documents, local repositories, everything under ~.

Thibault Sottiaux, who leads Codex at OpenAI, shared the preliminary findings. His statement is short enough to quote in full, and worth reading closely:

On file deletions. We've investigated a handful of reports where GPT-5.6 unexpectedly deleted files. What we have found is that this most commonly occurs when:

  • Full access mode is enabled and codex is run without sandboxing protections, including without auto review being enabled
  • The model attempts to override the $HOME env var to define a temporary directory.
  • The model makes an honest mistake and mistakenly deletes $HOME instead.

This is of course not how we want the system to behave, even when a user operates the model in full-access mode without the safeguards of our sandbox or without using auto review which checks for these kinds of high risk actions and rejects them. We are taking steps to mitigate this risk including by updating the developer message, guiding more users towards safer permission modes, and adding additional harness safeguards. Even though this happens extremely rarely, we'll share a detailed post-mortem in the coming days that goes into more details and what we are doing to minimize risks further.

— Thibault Sottiaux, Codex lead, OpenAI

Three facts are load-bearing here, and each deserves scrutiny:

  1. Every confirmed incident had the safety stack disabled. Full-access mode, no OS sandbox, no auto review. Three independent layers, all off.
  2. The trigger is an environment-variable override. The model wasn't trying to delete anything important. It was trying to create an isolated scratch directory by pointing $HOME somewhere else — a real technique with legitimate uses.
  3. The failure is characterized as an honest mistake. This was not prompt injection, not misalignment, not the model "going rogue." It's the agentic equivalent of a fat-fingered rm -rf — which is precisely why it's so important. You cannot train away fat fingers. You can only contain them.

Why a coding agent overrides $HOME at all

To readers outside the shell-scripting trenches, "the model reassigned $HOME" sounds bizarre. It isn't. It's a common isolation idiom, and the model almost certainly learned it from millions of legitimate examples.

Enormous amounts of Unix software treat $HOME as the root for per-user state: ~/.gitconfig, ~/.npmrc, ~/.cache, ~/.config, ~/.local/share. When you want to run a tool without letting it read or pollute the user's real configuration — in tests, in CI, in reproducibility checks — the standard trick is:

# classic isolation idiom — seen in thousands of CI scripts and test suites
export HOME=$(mktemp -d)
git config --global user.email "test@example.com"   # writes to the fake HOME
npm install --cache "$HOME/.npm"                     # caches in the fake HOME
./run-tests.sh
rm -rf "$HOME"                                       # clean up the scratch dir

Note the last line. In this idiom, rm -rf "$HOME" is correct and intentional — because within this script, $HOME is the throwaway directory. The training corpus is full of examples where deleting $HOME is the right thing to do. That's the trap: the safety of the command depends entirely on invisible state — what $HOME happens to resolve to at the instant of execution — and that state lives outside the text of the command.

A human running this script in one terminal session gets away with it, because the shell that set the variable is the shell that deletes it. An agent operating through a harness frequently does not get that guarantee. Which brings us to the mechanics of the failure.

Reconstructing the failure

A note on epistemics

OpenAI has confirmed the three bullet points above but has not yet published the command-level post-mortem. What follows is Harness Watch's technical reconstruction of the most plausible mechanisms, based on the confirmed facts, documented Codex architecture, and well-understood shell semantics. We'll update this analysis when the official post-mortem ships.

There are three well-known ways "override $HOME, then clean up" turns into "delete the real $HOME." All three are consistent with Sottiaux's description; the true incidents may include more than one.

Mechanism 1: State divergence across tool calls

Agent harnesses execute shell commands as discrete tool calls. In most harness designs — Codex included, in its default configuration — each tool call runs in a fresh process. Environment mutations do not persist from one call to the next unless the harness explicitly replays them.

The model, however, reasons over the transcript, not the process table. It sees that it ran export HOME=/tmp/scratch.XYZ four tool calls ago and — reasonably, by textual logic — believes the override is still in effect. Then it cleans up:

# Tool call N: (new process — the export from call N-4 is gone)
# Model believes: HOME=/tmp/scratch.XYZ
# Reality:        HOME=/Users/you
rm -rf "$HOME"

The command is perfectly quoted, syntactically flawless, and catastrophic. The model's world-model of shell state has silently diverged from actual shell state. This is arguably the signature failure mode of LLM agents in stateful environments: the model is a next-step predictor conditioned on text, and nothing in the text marks the moment where the environment reset.

Mechanism 2: Restore-then-delete ordering

Even within a single persistent session, the cleanup sequence itself is a footgun. A conscientious agent that overrode $HOME "knows" it should restore the environment when it's done. If it restores before it deletes:

export REAL_HOME="$HOME"          # save the original
export HOME=/tmp/scratch.XYZ      # override
# ... work happens ...
export HOME="$REAL_HOME"          # tidy! restore the environment first
rm -rf "$HOME"                    # ...then delete "the scratch dir"

Two individually-defensible steps — "restore the env" and "delete the scratch dir via the variable that named it" — compose into home-directory deletion purely because of ordering. This reading fits Sottiaux's phrasing exactly: the model "mistakenly deletes $HOME instead," i.e., the deletion targeted the variable at a moment when the variable had reverted to the real home.

Mechanism 3: Empty or unset expansion

The oldest member of this bug family. If the scratch path was itself derived from a variable that is unset in the current process (see Mechanism 1), expansions go degenerate:

# $SCRATCH was set in an earlier tool call; in this process it's empty
export HOME="$SCRATCH/build"      # HOME is now "/build"
rm -rf "$HOME"                    # attempts to delete /build

# or, unquoted and even worse:
rm -rf $SCRATCH/*                 # expands to: rm -rf /*

This is the same defect class that has bitten human-authored software for decades — the Steam-on-Linux bug of 2015 famously ran rm -rf "$STEAMROOT/"* with an empty $STEAMROOT and wiped users' drives. The lesson then is the lesson now: any destructive command whose target is computed through variable expansion is a loaded gun, and neither humans nor models can be trusted to always check the chamber.

The full failure chain

1
User enables full-access mode. The OS sandbox (Seatbelt on macOS; Landlock/seccomp on Linux) is disabled. Every command the model emits now runs with the user's full filesystem privileges.
2
Auto review is off. The secondary check that inspects proposed commands for high-risk actions — and would reject a destructive operation on $HOME — is not running.
3
The model needs an isolated workspace and reaches for a common idiom: reassign $HOME to a temp directory.
4
Model state and shell state diverge — via a fresh process, a restore-then-delete ordering, or an empty expansion.
5
rm -rf "$HOME" executes against the real home directory. rm asks no questions. There is no trash, no snapshot, no undo. The blast radius is everything the user's account can write to.

Notice what is absent from this chain: adversarial input, jailbreaks, misaligned objectives. Every link is either a user configuration choice or a bog-standard software bug pattern. That's what makes this incident more instructive than any prompt-injection demo — it's the base-rate failure, the one that scales linearly with usage.

The defense-in-depth ledger

The engineering question isn't "why did the model make a mistake?" Models make mistakes at some rate; GPT-5.6's rate on this pattern is evidently low ("extremely rarely") but non-zero, and non-zero times millions of sessions is a steady drip of destroyed home directories. The question is: how many independent layers stood between the mistake and the damage, and why was the answer zero?

LayerWhat it would have doneState during incidents
OS sandbox Filesystem writes confined to the workspace; unlink() outside it fails with EPERM regardless of what the model intended. Disabled
Auto review Secondary model/policy inspects each command; rm -rf on $HOME or any env-var-derived path outside the workspace gets rejected before execution. Disabled
Permission mode Default/safer modes require human approval for destructive or out-of-workspace commands. Full access
Harness command policy A static, non-bypassable denylist for catastrophic patterns (rm -rf against $HOME, /, ~) that survives even full-access mode. Did not exist
Reversible deletion Deletions routed to trash / an FS snapshot taken per-session; the mistake becomes an inconvenience instead of a loss. Did not exist
Developer-message guidance Instructs the model to use mktemp -d with a dedicated variable and never reassign $HOME; lowers the base rate of the triggering idiom. Not in place (now being added)

The first three rows are user-disableable, and the users in question disabled them. That is a legitimate choice for containers and throwaway VMs — full-access mode exists precisely because sandboxes break real workflows (network access, global toolchains, cross-directory builds). The critique is not that full-access mode exists. It's the bottom three rows: the layers that should survive full-access mode didn't exist.

What would actually have prevented it

1. A floor that no mode can turn off

"Full access" should mean "the model can do dangerous things," not "the harness stops looking." Even in the most permissive mode, a harness can cheaply maintain a small set of never-without-explicit-confirmation patterns: recursive deletion of $HOME, /, the workspace root's parents; mkfs; dd to block devices; chmod -R on system paths. This is not a sandbox and doesn't pretend to be one — it's a tripwire. Tripwires are allowed to have false negatives; their job is to make the common catastrophic mistakes require a human "yes." Crucially, the check must run on the resolved command, which brings us to:

2. Canonicalize before you execute

The fatal command never contained the string /Users/you. It said $HOME — the danger only exists after expansion. A harness that pattern-matches on raw command text will miss every variant of this bug. The fix is mechanical: before executing, expand the command's variable references against the actual environment of the process about to run it (or run the check inside that process), canonicalize the resulting paths, and evaluate policy against real targets. Cheap insurance in the same pass: flag destructive commands whose targets came from expansion at all — rm -rf of a literal path is auditable by a reviewer; rm -rf "$VAR" is auditable by nobody, including the model that wrote it.

3. Make deletion reversible by default

The deepest fix is to stop treating unlink() as an acceptable primitive for an agent that is wrong 0.01% of the time. Options ranked by cost:

4. Close the state-divergence gap

Mechanism 1 — the model believing an export persisted when it didn't — is a harness truthfulness problem, and it's fixable at the harness layer. If tool calls run in fresh processes, say so in the tool result, every time, mechanically: "note: environment resets between calls; HOME=/Users/you at start of this call." Injecting the actual values of security-relevant variables ($HOME, $PWD) into each tool result costs a few tokens and collapses the gap between model belief and reality. Alternatively, persist the environment faithfully and consistently — either contract works; the killer is ambiguity.

5. Train and instruct against the idiom itself

OpenAI's first announced mitigation — updating the developer message — targets step 3 of the chain: don't reassign $HOME; use mktemp -d into a dedicated variable (SCRATCH=$(mktemp -d)); prefer tool-native isolation (git -c, npm --prefix, XDG_* overrides) over wholesale $HOME substitution; never pass a bare variable as the target of a recursive delete — use rm -rf "${SCRATCH:?}", whose :? expansion aborts if the variable is unset or empty. Prompt-level fixes are the weakest layer — they lower the base rate rather than capping the blast radius — but they're also the cheapest to ship, which is presumably why they shipped first.

6. Price the modes honestly

The last layer is product design. Users disabled three safeguards, and it's worth asking how much friction stood in the way, and how legible the trade was at the moment of choice. "Full access" reads as a convenience upgrade; "every command this model emits will run unreviewed with the ability to irreversibly delete every file your account can write to" reads as what it is. Guiding users toward safer modes — OpenAI's second announced mitigation — matters most at exactly this moment of mode selection, and the honest framing of full-access-without-sandbox is: only inside a container or VM you're prepared to lose.

What OpenAI is doing

Per Sottiaux's statement, three workstreams: updating the developer message (layer 5 above), guiding more users toward safer permission modes (layer 6), and "additional harness safeguards" — unspecified, but the phrase most naturally covers some combination of layers 1–3. The promised post-mortem should clarify which. The questions we'd most like it to answer:

If you run coding agents — any coding agent

None of this is unique to Codex or GPT-5.6. Every agent harness — Codex, Claude Code, Cursor, Copilot, the open-source fleet — has a permissive mode, and every frontier model will occasionally compose two correct-looking shell commands into a disaster. Until reversibility is table stakes, the operator's checklist is:

The bigger picture

The instinct will be to file this under "AI safety incident," but the more useful frame is older: this is what happens when a new class of operator — fast, tireless, mostly-right — is handed interfaces designed for slow, careful, accountable humans. rm was designed for an operator who feels fear. The shell's environment-variable indirection was designed for an operator who holds state in their head across an entire session. LLM agents are neither, and the 2015 Steam bug proves humans aren't reliably either.

The fix, then, isn't a smarter model — GPT-5.7 will fat-finger something too, as will every model after it. The fix is harnesses that assume some small fraction of commands will be confidently, syntactically-perfectly wrong, and that bound the cost of that fraction: canonicalize, tripwire, snapshot, undo. The measure of a harness is not how well it performs when the model is right. It's the price it pays when the model is wrong.

We'll cover the full post-mortem when it lands.


Harness Watch covers the safety, reliability, and engineering of AI coding agents. Corrections and tips: this analysis will be updated as OpenAI publishes further details. Sections labeled as reconstruction are our analysis, not confirmed findings.