Skip to content

/plan critic + draft scratch escapes the workspace contract into the shared ~/data root — 67 stray entries; bare-root escape is now 3-for-3 across every bootstrap in agent-worktree-contract.sh #3926

Description

@tomerweller

/plan critic + draft scratch escapes the workspace contract into the shared ~/data root — 67 stray entries, and this is now 3-for-3 across every bootstrap in agent-worktree-contract.sh

Surfaced by /monitor-tick at 00:04Z while attributing a new bare-root directory on the shared volume.

This is the third family of the same escape, and the three together are the real finding:
scripts/lib/agent-worktree-contract.sh defines exactly three workspace bootstraps, and all three
have bare-root escapes on disk right now
:

bootstrap line mandated prefix bare-root entries observed issue
plan_critic_bootstrap :130 ~/data/$SESSION_ID/plan-$ISSUE/ 67 (0.709 MiB) this issue
review_pr_bootstrap :191 ~/data/$SESSION_ID/review-pr-$ISSUE/ 18 (190,339 B) #3921
do_bootstrap :258 ~/data/$SESSION_ID/do-$ISSUE/ 63 dirs (84.307 GiB) #3925

Three independent skills, three independent bootstraps, all correctly written, all bypassed. That
pattern is much stronger evidence for a shared root cause than any one family was on its own —
see Suggested fix below.

Symptom

$HOME/data holds 67 stray bare-root plan-* entries (10 dirs + 57 files), 743,368 B =
0.709 MiB
, accumulating since 2026-06-04. The newest, plan-3792/, was created at
2026-08-24T00:04:29Z — between two consecutive monitor ticks, so this is live, not residue.

Ten directories:

157,337 B  plan-3279            2026-06-15 19:33:53
 64,708 B  plan-3811            2026-08-09 06:40:36
 55,245 B  plan-3801            2026-08-09 05:18:43
 40,225 B  plan-3768-scratch    2026-08-11 13:12:57
 40,002 B  plan-3219            2026-06-16 19:11:07
 25,485 B  plan-3752-scratch    2026-08-10 21:22:46
 24,685 B  plan-3835            2026-08-10 18:22:56
 24,276 B  plan-3444            2026-06-19 05:05:05
 12,903 B  plan-3792            2026-08-24 00:04:29   <- created during this tick
  4,096 B  plan-3434            2026-08-08 22:56:15

plan-3792/draft.md (8,807 B) is a round-1 plan draft for #3792 — it opens
## 📝 Plan Draft (Round 1) and its body is a concrete implementation plan for the overlay
broadcast-drop metric bridge, i.e. genuine /plan output, not a stray temp file.

There is also one adjacent bare-root dir with a re-prefixed name, replan-3365/ (2026-06-17),
which I mention for completeness but have not counted in the 67.

Why this is off-contract

.claude/skills/plan/SKILL.md:110-122 states the invariant and even spells out the enforcement:

plan_critic_bootstrap validates and exports WORKTREE_BASE, CARGO_TARGET_DIR, and
CRITIC_WORKTREE. Pre-seeded values are accepted only if they resolve under the real ~/data
AND match the expected session/issue prefix ($HOME/data/$SESSION_ID/plan-$ISSUE/...).

and the helper cannot produce a bare root path.
scripts/lib/agent-worktree-contract.sh:130-152:

plan_critic_bootstrap() {
  local issue="$1"
  local critic_id="$2"
  local session_id="${CLAUDE_SESSION_ID:-${SESSION_ID:-$(date +%Y%m%d-%H%M%S)}}"
  ...
  local expected_prefix="$real_home/data/$session_id/plan-$issue"
  ...
  local candidate_base="${incoming_base:-$real_home/data/$session_id/plan-$issue}"
  if ! WORKTREE_BASE="$(require_home_data_path "$candidate_base" "WORKTREE_BASE")"; then
  ...
  if [[ -n "$incoming_base" ]]; then
    if ! require_session_prefix "$WORKTREE_BASE" "$expected_prefix" "WORKTREE_BASE"; then

session_id has a three-level fallback terminating in $(date +%Y%m%d-%H%M%S), so it is never
empty, and require_session_prefix (:109) re-validates any pre-seeded WORKTREE_BASE against
expected_prefix. A path of the form ~/data/plan-3792 — one level, no session segment — is
unreachable through this function. Something is writing plan artifacts without going through the
bootstrap.

Note the SKILL.md section immediately following the bootstrap is titled "Forbidden scratch patterns
(issue #2843 — hard requirement)"
and enumerates the repo-tree / <repo>-pr<N> / /tmp leaks. The
bare ~/data root is not in that enumeration — which is plausibly why all three families slip
through the same gap.

The contract-correct shape is achievable and is being achieved

23 session-scoped plan-* entries exist, so this is per-pass, not structural:

/home/tomer/data/20260728-034145/plan-3756
/home/tomer/data/20260809-094024/plan-3795
/home/tomer/data/20260619-001954/plan-3588
/home/tomer/data/20260619-001954/plan-3591
/home/tomer/data/20260619-001954/plan-3518-{draft,criticA,criticB,criticC,converged}.md
…

Same skill, same helper, correct prefix. The 20260619-001954/plan-3518-* set is especially useful:
it is the same artifact naming as the bare-root plan-3093-critic-{a,b,c}.md family, just placed
correctly — so the artifact writer clearly can take a session-scoped base when it is given one.

Five name shapes, i.e. more than one bypass site

 36  plan-<issue>-<artifact>.md      (-draft, -draft1, -final, -converged, -disagreement, -comment)
 18  plan-<issue>-critic-{a,b,c}.md
  8  plan-<issue>                    (plain template, session segment dropped)
  3  plan-<issue>-crit{A,B,C}.md     (a second critic-naming convention)
  2  plan-<issue>-scratch

plus plan-3801-issue.json (11,278 B) and the replan-3365/ dir. Two different critic-file
naming conventions (-critic-a.md vs -critA.md) in the same bare-root pool is itself evidence of
more than one writer.

Impact — hygiene and contract integrity, not runway

I sized the pool before framing it, because the sibling families differ by five orders of magnitude:

What it does cost is the same three things as #3921:

  1. The /review-pr sub-agents create huge worktrees in /tmp (11GB seen, near-OOM root FS) #2843 isolation guarantee is not held. Root-level scratch sits outside any session
    prefix, so require_session_prefix's poisoning protection never applies to it, and nothing
    expires it — there is no session dir to go away.
  2. Cross-run collision. ~/data/plan-3792 is a fixed path, so two concurrent plan rounds on the
    same issue share one directory. The -scratch suffixed variants (plan-3752-scratch,
    plan-3768-scratch) read like someone already worked around this.
  3. Data-root attribution noise. This issue exists because a routine bare-root directory delta
    had to be chased down before it could be explained.

Suggested fix — fix it once, at the shared layer

Because the escape now reproduces across all three bootstraps, per-skill patching is the wrong
shape. Two changes that would cover every family at once:

  1. Extend assert_no_repo_tree_scratch (agent-worktree-contract.sh:329) with a bare-root
    pattern.
    It is already the detection-only, no-deletion guard over an enumerated leak-pattern
    list, and its docstring calls itself "a fixed enumeration of the OBSERVED leak patterns". The
    bare ~/data root is now an observed leak pattern, three times over. A single check rejecting
    ^$HOME/data/(plan|review-pr|do)-[^/]*$ would have caught all 148 entries across the three
    issues.
  2. Find the artifact writers that compose paths inline rather than deriving them from the
    bootstrap's exported CRITIC_WORTREE/WORKTREE_BASE. For /plan specifically, the draft /
    critic / converged writes are the suspects: the bootstrap exports CRITIC_WORKTREE, but the
    round artifacts land as siblings named plan-<issue>-<artifact>.md, which suggests they are
    written by a path template that never sees $SESSION_ID.

Adding the bare-root pattern to #2843's forbidden-scratch enumeration in each SKILL.md would also
close the documentation gap that let three independent skills converge on the same mistake.

Existing entries are co-tenant data; the monitor has not deleted any of them and is not
proposing that they be swept without an owner's sign-off.

Non-urgent — no validator impact, no label. For context: the mainnet validator is down on the
unrelated #3910 outage (39h28m, build_sha 0ac84d42, deploys held by the blocked_active
quarantine); nothing in this issue bears on that.

Related: #3921 (review-pr bare-root class, same shape), #3925 (do-* bare-root worktree class,
same shape, 84.3 GiB), #2843 (the isolation contract these all violate).

Filed by /monitor-tick.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions