Skip to content

Lens isolation is scoped to the handed tree, but a lens can name any path — two occurrences reached outside it #712

Description

@topij

Graduated from the friction-log inbox (entries dated 2026-09-07). Severity M as logged.

Two occurrences in one session, from independent lenses, by two different mechanisms, both self-disclosed or caught by the cockpit. That is what makes it a contract/engine gap rather than a lens error — the same reasoning #574 uses for its own pair.

#574 covers a lens writing into the tree it was handed. These are the adjacent case: the isolation the panel arranges is a worktree, and neither occurrence was defeated by writing into that worktree.

Occurrence 1 — cp -a of a linked worktree does not copy git, it aliases it

An adversarial lens on PR #708 built its mutation scratch with cp -a of the linked worktree it was handed. A linked worktree's .git is a pointer file naming a per-worktree admin directory; cp -a copies the pointer, not the admin dir. So git stash and git checkout run inside the "copy" wrote straight into the given tree's index.

File contents were never altered. The lens detected it, repaired it with an index-only git reset, and disclosed it. The cockpit then independently confirmed the tree clean, the index empty, HEAD unmoved and both changed files hash-matching kit-manifest.json.

The contract's wording is satisfied by the command that breaks it. No writes in the tree you were given says to use "an absolute path outside the given tree" — cp -a /given/tree /absolute/path/outside complies exactly, and still writes the given tree.

Occurrence 2 — a relative path landed in the cockpit's own repository root

A lens reproducing a shell-parsing bug created bad2.sh by a relative path. It landed in /Users/topi/Coding/agentic-dev-kit — the cockpit's live checkout, which no lens was handed and which is not any lens's worktree.

It was never committed, and every branch tip was checked clear of it. But the cockpit stages with git add -A, so it was one commit from shipping into the kit.

The contract already describes this exact failure, verbatim: "a relative extract path lands in the repo root, where it sits untracked until some later git add -A commits it." So the wording is not the gap here either.

Why the pair is the point

Six later lens launches in the same session stated both hazards inline, ahead of the full contract, and neither recurred. That is the #469 shape — carrier, not wording — and it is evidence, not proof, since those launches also differed in other ways.

The deeper question the pair raises: isolating a lens by tree is the wrong unit when the lens can name any path it likes. A worktree bounds what the lens is pointed at, not what it can reach. Both occurrences reached outside it while complying with the sentence that was supposed to prevent that.

Options

  1. State both mechanisms in No writes in the tree you were given — cheapest, and the least likely to hold, since the wording already covered occurrence 2 and was already complied with in occurrence 1.
  2. Have panel_prompt.py state them early, beside the tree it hands over, the way #469's fix put the fresh-path rule ahead of the contract. Matches where the session's own mitigation appeared to work.
  3. Give the lens a scratch directory it is told to use, rather than a rule about paths it must not use — replaces a prohibition the lens must remember with a destination it is handed.
  4. Bound the lens by something other than the tree. Largest change, and the only one that addresses the unit rather than the instances.

Cross-references #574 (a lens writing into the handed tree, via git fetch on shared refs) and #469 (carrier rather than wording). Neither is a duplicate: #574's mechanism is shared refs inside the given tree; this is two mechanisms that reach outside it.

https://claude.ai/code/session_019VqvhqHbSiZaLJjUnoy3nY

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions