DEV Community

Quinn Sun
Quinn Sun

Posted on

The Senior Pinned cwd Before the Agent Touched the Tree

The screen share stopped on a clean git status. Green. Empty. The junior treated that empty tree as permission to paste a refactor prompt into a remote coding agent.

The senior did not. A clean tree on one laptop is not a lease on the machine that will actually write the files. The pairing spent the next hour making that distinction boring and enforceable. The session below is a reconstructed teaching example, not a claim about a named outage or a measured benchmark.

The ticket looked small

The change was a logging helper in a Node service. One module. Tests already existed. The junior had a prompt ready: extract the helper, keep the public API, run the unit tests.

The senior asked for the process location first. Not the design. Not the model. The location. Until cwd had a written contract, the prompt stayed in the paste buffer.

Questions that landed on the shared doc

The senior's questions were written down before any agent ran:

  1. Which machine executes the write?
  2. Which absolute path is the repo root on that machine?
  3. What is already dirty in that checkout?
  4. Which paths may the agent create or edit?
  5. How does the session prove what it touched?
  6. What happens to leftover files when the session dies?

None of those items are model questions. They are filesystem questions. The pairing kept treating them as filesystem questions even when the agent replies sounded confident.

Dead end 1: run it locally because it is simpler

Local execution made the path obvious. pwd matched the editor. Tests ran in the same shell. The junior called that a win.

It also loaded the developer's environment into the session. Shell history. An .env sitting one directory up. A cloud credential file the agent did not need and should not see. Convenience was not a workspace contract. The senior rejected the local run for this ticket and the pairing moved on.

Dead end 2: a shared remote directory with an almost clean status

A teammate offered a cloud box that already had the repo. git status --porcelain showed one untracked notes.md and a half-applied stash. Close enough, in the junior's phrasing.

The agent would have treated those files as context. It might have committed them. It might have deleted them as cleanup. Shared dirt is not a starting line. The senior stopped the session before the first prompt.

Dead end 3: let the agent pick a temp directory

The next idea was a fresh mktemp -d, clone, patch, copy the diff back. Isolation looked good on a whiteboard.

Then a clone failed halfway in a later dry run of the same idea. The diff never returned. The only record was a chat transcript that claimed the tests passed. There was no git object to review. A temp directory without a report path is a disappearing working tree. The pairing discarded that pattern for any change that needed a reviewer.

The decision that survived

The pairing kept one rule. The agent does not start until a workspace lease file exists in the repo. The session does not end until a checker confirms the working tree stayed inside that lease.

The lease is data. Not a speech in the prompt. Not a vibe. A file the checker can parse after the model has gone quiet.

The lease file

Create agent-lease.json at the repo root. Keep it committed so every session, local or remote, reads the same boundary.

{
  "repo_root": ".",
  "allowed_write": ["src/", "tests/", "package.json"],
  "forbidden": [".env", ".env.local", "secrets/", ".git/", "node_modules/"],
  "max_untracked": 0
}
Enter fullscreen mode Exit fullscreen mode

The junior wanted max_untracked at 5 for scratch files. The senior set it to 0. Scratch belongs outside the repo, or it does not belong. The logging helper did not need a souvenir JSON dump from the agent.

The checker

A small bash script is enough for a first version. Label this as a teaching example and run it on a throwaway branch first. It is a working-tree filter, not a sandbox.

#!/usr/bin/env bash
# check-agent-lease.sh — teaching example, not a security boundary.
set -euo pipefail

cd "$(git rev-parse --show-toplevel)"
fail=0

while IFS= read -r line; do
  [[ -z "$line" ]] && continue
  path="${line:3}"
  path="${path#\"}"
  path="${path%\"}"
  path="${path##* -> }"

  case "$path" in
    .env|.env.local|secrets|secrets/*|.git|.git/*|node_modules|node_modules/*)
      echo "forbidden: $path"
      fail=1
      continue
      ;;
  esac

  case "$path" in
    src|src/*|tests|tests/*|package.json)
      ;;
    *)
      echo "outside lease: $path"
      fail=1
      ;;
  esac
done < <(git status --porcelain)

untracked="$(git status --porcelain | awk '/^\?\? / {c++} END {print c+0}')"
if (( untracked > 0 )); then
  echo "untracked $untracked > max_untracked 0"
  fail=1
fi

if (( fail == 0 )); then
  echo "lease ok"
  git status --porcelain || true
fi
exit "$fail"
Enter fullscreen mode Exit fullscreen mode

The matcher is blunt on purpose. Nested packages, submodules, and generated files need explicit entries. The pairing accepted blunt over clever for v1 and wrote that limitation next to the script.

Commands the session ran

Before the agent:

git checkout -b pair/log-helper-lease
git status --porcelain
git rev-parse --show-toplevel
test -f agent-lease.json
chmod +x check-agent-lease.sh
./check-agent-lease.sh
Enter fullscreen mode Exit fullscreen mode

After the agent, still before human review:

./check-agent-lease.sh
git diff --stat
git diff -- src tests
Enter fullscreen mode Exit fullscreen mode

If the checker exits non-zero, the diff does not get a review. It gets reset on the session branch.

git reset --hard HEAD
git clean -fd -- src tests
Enter fullscreen mode Exit fullscreen mode

git clean is destructive. The pairing only ran it on the branch created for the session. Main never saw the experiment.

The report the senior accepted

Accepted porcelain, after the helper landed:

 M src/log.js
 M tests/log.test.js
Enter fullscreen mode Exit fullscreen mode

Rejected porcelain from a dry-run the pairing threw away:

 M src/log.js
?? debug-session.json
?? .env.local
Enter fullscreen mode Exit fullscreen mode

The second report failed for two independent reasons. An untracked debug file broke max_untracked. A forbidden env file would have failed even if it were tracked. The senior kept both checks. One without the other still lets junk ride along.

A CI job that repeats the same check

# .github/workflows/agent-lease.yml
name: agent-lease
on: [pull_request]
jobs:
  lease:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check working tree against lease
        run: |
          chmod +x check-agent-lease.sh
          ./check-agent-lease.sh
Enter fullscreen mode Exit fullscreen mode

This job does not score model quality. It only refuses a pull request whose committed tree grew outside the lease. That is a smaller promise. It is the one the pairing was willing to keep.

CI sees the committed snapshot, not the live remote shell. The session still has to run the checker on the machine that wrote the files, before git add.

Prompt preamble, kept short

The agent still needs text. The pairing put the lease next to a short preamble so the model was not the source of truth.

Read agent-lease.json before any edit.
Write only under allowed_write.
Never read or write forbidden paths.
Do not create files to help the next session.
When finished, print git status --porcelain and stop.
Enter fullscreen mode Exit fullscreen mode

The preamble is not the control. The checker is the control. If they disagree, the checker wins and the branch is reset.

Where a free remote session fits

Some tickets are easier when the model and the shell are not on the developer's laptop. Procurement is slow. A shared dirty box is worse. A remote checkout with a lease is the remaining option the pairing did not throw away.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode's free model access and free server option participate in this method only as a place to run the same lease: a checkout, a model, a shell, then check-agent-lease.sh. The lease file stays in git. The vendor is optional. If the remote box still has leftover files from a previous session, the checker is supposed to fail closed.

No model names, quotas, hardware, or duration claims are made here. Those change. The lease does not care which model wrote the patch. It cares which paths changed.

Decision table left on the wiki

Option When it is acceptable Failure the lease must catch
Local agent Secrets already isolated; laptop is the only writer Edits outside allowed_write
Dedicated remote checkout One ticket, one branch, one cwd Clone drift; leftover untracked files
Shared remote directory Almost never Cross-ticket contamination
Anonymous temp dir A spike that will be thrown away Missing diff; unverifiable tests
Free remote server plus lease file Exploratory patch that still yields a git object Dirty start; writes outside the lease

The table is a pairing artifact, not a vendor ranking. Move a row when the repo's risk changes. Do not move a row because a chat window felt fast.

Limitations

The lease is not a sandbox. A process that ignores the file can still write anywhere the OS user can write. Containers, extra users, and network egress rules are separate controls. This workflow does not replace them.

The path matching in the teaching script is incomplete. Monorepos, generated fixtures, and submodule pointers need explicit allowed_write entries before the first session. A false fail is cheaper than a silent file in secrets/.

Who should not use this approach:

  • Anyone treating a prompt preamble as a security boundary
  • Workflows that must touch secrets, production data, or credentials
  • Teams that will not reset a branch when the checker fails
  • Changes that require writes across the whole tree without an updated lease

The pairing ended with a boring outcome. The logging helper shipped from a branch whose git status --porcelain matched the lease. The interesting part was not the helper. It was the refusal to start until cwd had a file.

Copy the lease and the checker into the repo first. Start the agent second.

Top comments (0)