DEV Community

Sam Chen
Sam Chen

Posted on

Five Workspace Anti-Patterns That Fake Agent Skill

Most so-called coding-agent wins are workspace luck. The model looks sharp because the tree was dirty. Did the agent reason well, or did leftover files help?

I stop blaming the model on first contact. I audit the working tree before I touch prompts. This catalog is about workspace contracts, not model lore.

Each entry lists symptoms, a root cause, and a replacement. Steal the gates. Skip the slogans. Ask one question before you swap vendors.

Was the workspace even bounded this time?

How to read each entry

Use one anti-pattern per incident, not five. Stacking fixes hides the real leak. I treat these gates like tests, not vibes.

If a demo only works on a warm directory, it is not a demo. Cold clone it. Then talk about models.

Anti-pattern 1: Live-tree edits

The agent writes straight into your daily checkout. No branch. No worktree. No disposable clone. Failure and success share the same files.

Symptoms

  • git status turns noisy after a "small" task
  • Demo files mix with your real local changes
  • A failed run leaves half a refactor behind

Root cause

You treated the repo as a chat scratchpad. The agent had no isolation boundary. Every tool call could own main.

Why should a bad patch keep your uncommitted work hostage?

Replacement

Give every run a throwaway worktree. Apply patches only after a gate passes. Keep your daily checkout read-only for the agent.

RUN_ID=$(date +%s)
git worktree add -b "agent/run-$RUN_ID" "/tmp/wt-$RUN_ID" HEAD
cd "/tmp/wt-$RUN_ID"
# agent may edit only this path
Enter fullscreen mode Exit fullscreen mode

I refuse runs that cannot name their worktree path. If you cannot delete the directory, you do not own the experiment.

Anti-pattern 2: Unbounded glob

The agent searches the tree like a disk dump. It reads secrets, lockfiles, and vendor junk. Context fills with files no human would open.

Symptoms

  • Tool logs show .env, node_modules, or .git
  • The model "discovers" keys you never pasted
  • Answers cite minified files you do not maintain

Root cause

Read tools default to convenience. Convenience is not an access policy. A shared machine does not make that safer.

Would you let an intern cat the whole laptop?

Replacement

Deny by default. Allow one path prefix. Reject reads that match a deny list. Log every blocked path, not only the allowed ones.

# proposal: a path gate, not a sandbox claim
from pathlib import Path

ALLOW_PREFIX = Path("src").resolve()
DENY_PARTS = (".env", ".git", "node_modules", "id_rsa")
DENY_SUFFIX = (".pem", ".p12", ".key")

def allowed(raw: str) -> bool:
    path = Path(raw).resolve()
    try:
        path.relative_to(ALLOW_PREFIX)
    except ValueError:
        return False
    text = path.as_posix()
    if any(part in text.split("/") for part in DENY_PARTS):
        return False
    return not text.endswith(DENY_SUFFIX)
Enter fullscreen mode Exit fullscreen mode

I want the blocked-path list in the transcript. Hidden denies teach nothing when the next run fails.

Anti-pattern 3: Partial writes as progress

The agent writes file A, then dies on file B. You keep A because tests looked "almost green." Drift becomes the new baseline.

Symptoms

  • One test file changed, the production file did not
  • Imports point at functions that do not exist yet
  • Re-runs stack more half-patches on the wreckage

Root cause

You scored steps, not transactions. A coding agent is a patch applicator. Half a patch is a fresh bug with extra confidence.

Is a half-merge a feature? It is not.

Replacement

Stage one unified diff. Apply it atomically. Reject the whole patch when any gate fails. Do not bargain with leftover hunks.

# proposal workflow: check, then apply, else restore
git diff --binary > /tmp/agent.patch
git checkout -- .
git apply --check /tmp/agent.patch
git apply /tmp/agent.patch
Enter fullscreen mode Exit fullscreen mode

I also keep the patch file beside the logs. If apply fails, the artifact is the patch, not a chat recap.

Anti-pattern 4: No revert oracle

The run ends. You eyeball the diff and shrug. Nobody can restore the pre-run tree without another model call.

Symptoms

  • "Undo that" means more agent chat, not git
  • You cannot name the parent commit for the run
  • CI looks green on a dirty local state

Root cause

You logged prompts. You did not log trees. Transcripts are not recovery tools. Porcelain status is.

Can you rewind without begging the model to remember?

Replacement

Snapshot before tools. Revert on red. Record HEAD and git status --porcelain as first-class outputs.

BEFORE=$(git rev-parse HEAD)
git status --porcelain > /tmp/status.before
# ... agent run ...
if ! ./gates.sh; then
  git reset --hard "$BEFORE"
  git clean -fd
  echo "reverted to $BEFORE"
fi
Enter fullscreen mode Exit fullscreen mode

Read that reset --hard line twice. It will destroy uncommitted work if you point it at the wrong tree.

Anti-pattern 5: Shared eval workspace

Several runs share one server directory. Yesterday's fixtures become today's context. You think the model improved. It remembered a file.

Symptoms

  • The agent "knows" files you never fetched
  • Wins vanish on a cold clone of the same commit
  • Two tasks overwrite the same fixture path

Root cause

A cheap remote directory feels like a playground. Playgrounds get contaminated. You evaluated residue, not the agent.

Did it pass because leftover fixtures whispered answers?

Replacement

One run, one directory, one wipe. Cold clone or a fresh worktree every time. Delete on exit, even after green.

RUN_DIR=$(mktemp -d)
git clone --depth 1 "$REPO" "$RUN_DIR"
trap 'rm -rf "$RUN_DIR"' EXIT
cd "$RUN_DIR"
Enter fullscreen mode Exit fullscreen mode

I do not trust a win I cannot delete and recreate. If step six in the plan still "remembers" files, stop scoring the model.

Artifact: gates, a table, and a test plan

This is a proposed checklist, not a published benchmark. I am not attaching timings, hardware, or token counts. Run it as a human-labeled test plan.

Decision table

Signal you actually saw Likely anti-pattern Do this first
Dirty main after a chat Live-tree edits New worktree
Secrets in tool traces Unbounded glob Prefix plus deny list
Missing symbols after a "win" Partial writes Atomic git apply
Cannot restore files No revert oracle Hard reset on red
Win disappears on a cold clone Shared eval workspace Fresh directory

Minimal gates.sh

#!/usr/bin/env bash
set -euo pipefail

if [[ -n "$(git status --porcelain)" ]]; then
  echo "refuse: dirty tree"
  exit 1
fi

git diff --check
[[ -f /tmp/status.before ]] || {
  echo "refuse: missing preflight status"
  exit 1
}
Enter fullscreen mode Exit fullscreen mode

Wire this after apply, not after a vibe check. A green model reply is not a gate.

Test plan I actually run

  1. Start from a clean clone of a known commit.
  2. Inject a fake .env outside src/.
  3. Confirm the path gate blocks that read.
  4. Build a two-file patch with one bad hunk.
  5. Confirm git apply --check rejects the whole patch.
  6. Confirm the tree matches HEAD after the reject.
  7. Re-run the same task in a second directory.
  8. Confirm leftover fixtures cannot leak into that run.

If step 7 still passes only on the warm directory, you tested contamination. You did not test the agent.

Where cheap inference actually helps

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

I use cheap inference to rerun gates, not to skip them. MonkeyCode's free model access and free server option are useful as a disposable place to repeat the checklist. The server is another workspace. It is not a policy engine.

Do not treat free compute as a license to share trees. These anti-patterns still apply on a free server. If the path gate fails on your laptop, it fails remotely too.

The only check I care about is boring. Can a stranger clone reproduce the win without your leftovers?

If you try the gates there, paste the porcelain log. Do not paste a model nickname.

Limitations

This catalog does not rank models. It does not claim quotas, uptime, or hardware. It will not save a repo that has no tests.

Skip this approach if you need any of the following.

  • Formal multi-tenant isolation with a real sandbox
  • A workflow that cannot use git worktrees
  • In-place edits on production checkouts
  • The model itself acting as the security boundary

A path prefix is not a seccomp jail. git reset --hard destroys work you forgot to commit. Atomic apply still needs tests that mean something.

What I run instead of another prompt tweak

  1. Bound the tree with a worktree or clone.
  2. Deny secret paths before the first read.
  3. Apply one atomic patch, or apply nothing.
  4. Revert on red using a recorded HEAD.
  5. Throw the directory away after the score.

Then, and only then, judge the model. Was the last "agent failure" a model failure? Or did you hand it main and a glob?

Top comments (0)