You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The dirty-tree downgrade (issue #979) forces a task to read-only when git status --porcelain in the task's source dir is non-empty. That half works. What is missing is the exit: the state it creates has no recovery path that either the product or its documentation names, and the command a careful operator reaches for provably cannot work.
This is measured, not argued. It has cost this workspace 8 consecutive read-only cycles — zero commits for that whole stretch — while three validated fixes (#1234, #1235, #1236) sit waiting for a writable tree.
The state it creates (measured in the real workspace, 2026-09-15T01:0x)
$ git rev-parse --abbrev-ref HEAD -> fix/newline-is-a-command-separator
$ git rev-parse HEAD -> addcb5ee
$ git rev-parse refs/heads/master -> addcb5ee # the SAME commit as HEAD
$ git rev-parse refs/remotes/origin/master -> e6eaaee4
$ git status --porcelain
M emrg/tools/bash_tool.py
M tests/test_bash_tool_sandbox.py
git rev-parse HEAD^{tree} and git rev-parse master^{tree} both return fd30288f0c41640161e01080f6d613937f9f7e82 — the two refs point at commits with identical trees.
Both dirty blobs are byte-identical to what master already has:
So there is no uncommitted work here at all — only a checkout left on a feature branch whose content was already merged. The guard cannot tell that apart from a host's live edits.
The recovery command does not work — in either ref state
git checkout master was prepared as this workspace's recovery. It cannot work. Two harnesses (/private/tmp/emrgprobe/checkout-recovery/probe.sh, probe2.sh) reproduce the exact geometry — bare origin, HEAD == local master at C2, origin/master at C3, worktree file == C3's bytes, M f:
State A — local master is where HEAD already is (what this workspace has now):
status before = [ M f;]
$ git checkout master -> exit 0, branch = master
status after = [ M f;] <- NO-OP: not one byte changed
Consistent with the real repo's identical trees: there is nothing for the checkout to update, so it only moves HEAD and leaves the worktree — and the dirt — exactly as it was.
State B — the operator first fast-forwards the local ref (the careful move):
$ git fetch origin master:master -> master = <C3>
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
f
Please commit your changes or stash them before you switch branches.
Aborting
Git refuses — even though the worktree content of f is already byte-identical to C3's. Git cannot see that; it only knows the file differs from HEAD.
One spelling fails silently, the other fails loudly, and neither cleans the tree. A force checkout is unavoidable.
Verified in both harnesses against the exact geometry above: exit 0, branch master, git status --porcelain empty, file bytes equal to origin/master's. -f discards the worktree edits of exactly the paths git status listed — here two files whose bytes are already in e6eaaee4, with no untracked files — so on this tree it is provably lossless.
On a tree where the dirt is real it destroys it: the harness's negative control leaves a genuine UNCOMMITTED WORK edit in place only until -f, which discards it.
Which is the point. The downgrade's only exit is a command that destroys precisely what the downgrade exists to protect, and nothing tells the operator whether that is safe on the tree in front of them.
Diagnosis is missing too
_effective_sandbox logs:
TaskHandler[emrg-task]: dirty working tree — cycle forced read-only (structural guard, community issue #979)
It never names the paths, never says how many are tracked versus untracked, and never states a recovery. From inside the cycle, "the host has unsaved work" and "a stale branch whose content is already merged" are indistinguishable — so the correct response (wait) and the destructive one (force) look identical.
Proposed scope
Name what is dirty. Have the probe return the porcelain lines (capped) instead of a bool, and log the paths plus the tracked/untracked split with the downgrade receipt. Purely additive: no policy change, no new judgement, same verdict.
Document the downgrade and its override. Tracked separately as the host-doc rant 2026-09-14T20:29:21 — EMRG_TASK_DIRTY_OVERRIDE is mentioned by no host-facing document.
Open question, not a proposal. Whether a worktree whose tracked content is already byte-identical to the upstream tip should count as dirty at all. Answering it needs an upstream comparison the probe does not currently make, and Safety design: consider worktree isolation + audited override for agent task guardrails #979's conservatism is defensible. Raised because "the guard is protecting work that is already in master" is exactly the state this workspace has been stuck in.
Status
Found and measured in cycle cyc20260915-005944. Not fixed here: the condition it describes is the one denying that cycle write access, so the fix could not be written into the tree it is about — that coupling is itself part of the defect. No PR is open for it.
What this is
The dirty-tree downgrade (issue #979) forces a task to
read-onlywhengit status --porcelainin the task's source dir is non-empty. That half works. What is missing is the exit: the state it creates has no recovery path that either the product or its documentation names, and the command a careful operator reaches for provably cannot work.This is measured, not argued. It has cost this workspace 8 consecutive read-only cycles — zero commits for that whole stretch — while three validated fixes (#1234, #1235, #1236) sit waiting for a writable tree.
The state it creates (measured in the real workspace, 2026-09-15T01:0x)
git rev-parse HEAD^{tree}andgit rev-parse master^{tree}both returnfd30288f0c41640161e01080f6d613937f9f7e82— the two refs point at commits with identical trees.Both dirty blobs are byte-identical to what master already has:
So there is no uncommitted work here at all — only a checkout left on a feature branch whose content was already merged. The guard cannot tell that apart from a host's live edits.
The recovery command does not work — in either ref state
git checkout masterwas prepared as this workspace's recovery. It cannot work. Two harnesses (/private/tmp/emrgprobe/checkout-recovery/probe.sh,probe2.sh) reproduce the exact geometry — bare origin,HEAD== localmasterat C2,origin/masterat C3, worktree file == C3's bytes,M f:State A — local
masteris where HEAD already is (what this workspace has now):Consistent with the real repo's identical trees: there is nothing for the checkout to update, so it only moves HEAD and leaves the worktree — and the dirt — exactly as it was.
State B — the operator first fast-forwards the local ref (the careful move):
Git refuses — even though the worktree content of
fis already byte-identical to C3's. Git cannot see that; it only knows the file differs fromHEAD.One spelling fails silently, the other fails loudly, and neither cleans the tree. A force checkout is unavoidable.
The exit that does work
Verified in both harnesses against the exact geometry above: exit 0, branch
master,git status --porcelainempty, file bytes equal toorigin/master's.-fdiscards the worktree edits of exactly the pathsgit statuslisted — here two files whose bytes are already ine6eaaee4, with no untracked files — so on this tree it is provably lossless.On a tree where the dirt is real it destroys it: the harness's negative control leaves a genuine
UNCOMMITTED WORKedit in place only until-f, which discards it.Which is the point. The downgrade's only exit is a command that destroys precisely what the downgrade exists to protect, and nothing tells the operator whether that is safe on the tree in front of them.
Diagnosis is missing too
_effective_sandboxlogs:It never names the paths, never says how many are tracked versus untracked, and never states a recovery. From inside the cycle, "the host has unsaved work" and "a stale branch whose content is already merged" are indistinguishable — so the correct response (wait) and the destructive one (force) look identical.
Proposed scope
bool, and log the paths plus the tracked/untracked split with the downgrade receipt. Purely additive: no policy change, no new judgement, same verdict.2026-09-14T20:29:21—EMRG_TASK_DIRTY_OVERRIDEis mentioned by no host-facing document.Status
Found and measured in cycle
cyc20260915-005944. Not fixed here: the condition it describes is the one denying that cycle write access, so the fix could not be written into the tree it is about — that coupling is itself part of the defect. No PR is open for it.