Agents treat the whole repo as in-scope. Extra paths then land inside the review diff. A 90-minute allowlist gate produces ship-or-kill evidence. Ship the gate when extra paths appear in git. Kill the agent loop if the gate only nags.
Hypothesis, one line
Cheap agent edits make surprise files cheap too. The spike tests a single claim. An unconstrained coding agent will touch paths outside a frozen allowlist.
Ship criterion: a local git gate fails those runs. Kill criterion: no extra paths, or the gate is noise. This is not a model ranking exercise. It is checkout discipline. Architecture talk can wait.
Why extra paths matter now
Agent write-ups still reward bigger loops. Cheap code also cheapens side edits. Refactors leak into configs. Lockfiles move without a request. Formatter noise hides the real patch.
Reviewers read the task, not the tree. Tests can pass on the wrong files. Debt arrives as a green CI. A path allowlist is a small contract. It is cheaper than another prompt.
Recent agent threads keep circling assumption errors. The filesystem is the assumption that ships. If the agent can see every package, it will “help” every package. Bound the tree before you bound the model.
Timer rules
Ninety minutes. One repo. One task. One gate.
In scope
- Freeze HEAD and a path allowlist
- Give the agent one concrete edit
- Run a post-diff path gate
- Write a four-line ship-or-kill note
Out of scope
- Prompt rewrites after the first fail
- New agent frameworks
- Latency charts
- Production rollout
Stop at minute 90. Incomplete evidence is still evidence. A missing kill note is a fail.
Minute 0–10: freeze the task
Pick a change that should touch two paths. Example: add a client timeout. Expected files stay tiny on purpose.
# allowlist.txt
src/http/client.ts
src/http/client.test.ts
Write the allowlist before the agent starts. Do not edit it after the diff. That late edit would destroy the spike.
git rev-parse HEAD > /tmp/spike-head
test -z "$(git status --porcelain)" || {
echo "dirty tree; abort spike" >&2
exit 1
}
cp allowlist.txt /tmp/spike-allowlist
The tree must be clean. Dirty files poison path evidence. Untracked junk looks like agent drift.
State the task in one paragraph. No architecture tour. No “also clean types”.
Example task card (proposal, unexecuted):
Add a 2500ms timeout to src/http/client.ts.
Cover it in src/http/client.test.ts.
Do not format other packages.
Do not touch lockfiles.
Swap those paths for your tree. Keep the card shorter than the allowlist.
Minute 10–30: build the gate
The gate is the artifact. Keep it local. Keep it boring. The script below is meant to run after the agent stops.
#!/usr/bin/env bash
# fail-extra-paths.sh
# Fail when git changes walk off allowlist.txt
set -euo pipefail
ALLOWLIST="${1:-allowlist.txt}"
BASE_FILE="${2:-/tmp/spike-head}"
MAX_LINES="${MAX_LINES:-200}"
if [[ ! -f "$BASE_FILE" ]]; then
echo "missing base sha file: $BASE_FILE" >&2
exit 2
fi
if [[ ! -f "$ALLOWLIST" ]]; then
echo "missing allowlist: $ALLOWLIST" >&2
exit 2
fi
BASE="$(tr -d '[:space:]' < "$BASE_FILE")"
mapfile -t allowed < <(grep -E -v '^(#|$)' "$ALLOWLIST")
if [[ ${#allowed[@]} -eq 0 ]]; then
echo "allowlist is empty" >&2
exit 2
fi
mapfile -t changed < <({
git diff --name-only "$BASE"
git ls-files --others --exclude-standard
} | awk 'NF && !seen[$0]++')
if [[ ${#changed[@]} -eq 0 ]]; then
echo "no changes against $BASE"
exit 3
fi
fail=0
extra=()
missing=()
for path in "${changed[@]}"; do
ok=0
for a in "${allowed[@]}"; do
if [[ "$path" == "$a" ]]; then
ok=1
break
fi
done
if [[ "$ok" -eq 0 ]]; then
extra+=("$path")
fail=1
fi
done
if [[ "${REQUIRE_ALL:-0}" == "1" ]]; then
for a in "${allowed[@]}"; do
found=0
for path in "${changed[@]}"; do
if [[ "$path" == "$a" ]]; then
found=1
break
fi
done
if [[ "$found" -eq 0 ]]; then
missing+=("$a")
fail=1
fi
done
fi
lines="$(git diff --numstat "$BASE" | awk '{i+=$1; d+=$2} END {print i+d+0}')"
if [[ "$lines" -gt "$MAX_LINES" ]]; then
echo "line budget exceeded: $lines > $MAX_LINES"
fail=1
fi
echo "base: $BASE"
echo "line churn: $lines (max $MAX_LINES)"
echo "changed paths:"
printf ' %s\n' "${changed[@]}"
if [[ ${#extra[@]} -gt 0 ]]; then
echo "extra paths (fail):"
printf ' %s\n' "${extra[@]}"
fi
if [[ ${#missing[@]} -gt 0 ]]; then
echo "missing allowlist paths (fail):"
printf ' %s\n' "${missing[@]}"
fi
if [[ "$fail" -ne 0 ]]; then
echo "SPIKE FAIL: extra paths, missing paths, or line budget"
exit 1
fi
echo "SPIKE PASS: diff stays on allowlist"
exit 0
Make it executable once. Do not pretty-print it later.
chmod +x fail-extra-paths.sh
Optional line budget sits in MAX_LINES. Default is 200. Tune only before the agent runs. Tuning after the diff is cheating.
Minute 30–70: run one agent pass
Use whatever coding agent you already trust. One pass. No retry storm. Paste the task card. Point it at the two files. Then stop typing.
A scratch host is enough for this loop. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option. Treat that pair as a disposable runner for the spike. Do not treat it as production capacity. The gate does not depend on that host. Any local agent checkout works the same.
If the agent asks to “clean adjacent files”, refuse. Adjacent files are the failure mode. If it rewrites the allowlist, that is also a fail. Freeze the contract outside the repo if needed.
# keep the contract outside the agent sandbox if the tree is writable
cp allowlist.txt /tmp/spike-allowlist
Do not watch tokens during this window. Token burn is a different spike. This spike only counts paths and line churn.
Minute 70–85: read the evidence
Run the gate against frozen HEAD. Do not rebase first. Do not stash agent junk.
./fail-extra-paths.sh /tmp/spike-allowlist /tmp/spike-head
echo "exit: $?"
git diff --stat "$(cat /tmp/spike-head)"
Classify every extra path. Use the table. Do not argue with the model.
| Extra path kind | Likely cause | Spike reading |
|---|---|---|
| Formatter / import sort in other packages | Repo-wide save hook | Gate stays; hook is in scope |
| Lockfile without a requested dep | Agent “helpfully” upgraded | Fail the run |
| README or changelog | Narrative padding | Fail the run |
| Snapshots you did not name | Test tooling drift | Fail, then tighten the card |
| The two allowlisted files only | Bound behavior | Pass |
| Zero files | Agent stalled | Exit 3; kill or retry once |
REQUIRE_ALL=1 catches the stall case. It also fails partial edits. Use it when both files are mandatory.
REQUIRE_ALL=1 MAX_LINES=120 ./fail-extra-paths.sh /tmp/spike-allowlist /tmp/spike-head
Record the exit code. Record extra paths verbatim. Screenshots are optional. The path list is not.
Minute 85–90: ship or kill
Write four lines. No blog voice. No vendor recap.
HYPOTHESIS: unconstrained agent will leave extra paths
RESULT: PASS gate / FAIL gate / NO CHANGE
EVIDENCE: <exit code>, <extra paths or none>, <line churn>
DECISION: SHIP path gate into the agent wrapper / KILL this workflow
Ship if extra paths appeared even once. The gate earned its keep. Kill if the agent stayed inside the list and the gate added only friction. Kill if the task was too vague to judge. Vague tasks are not model failures.
Do not “improve the prompt” after the clock. That is a second spike. Mix those and the evidence collapses.
Wrapper sketch, not a platform
If you ship, wrap the agent command. Keep stdin as the task card. Keep stdout as logs. Keep the gate as the process exit.
#!/usr/bin/env bash
set -euo pipefail
# Proposed wrapper. Not production. Not executed here.
allowlist="${ALLOWLIST:-allowlist.txt}"
git rev-parse HEAD > /tmp/spike-head
test -z "$(git status --porcelain)"
# replace with your agent invocation
"$@"
./fail-extra-paths.sh "$allowlist" /tmp/spike-head
The wrapper should fail closed. A crashed agent still leaves a diff. Run the gate on EXIT as well.
trap './fail-extra-paths.sh "$allowlist" /tmp/spike-head' EXIT
Trap plus set -e needs care. Test the trap on a dummy extra file. Do that inside the 90 minutes if time remains. Skip it if the clock is gone.
Limitations
The allowlist is exact paths, not globs. Generated folders will false-fail. Rename-heavy refactors will false-fail. Binary assets will blow MAX_LINES in odd ways. git diff --numstat reports - for binaries. The awk sum then undercounts. Add a binary check if your tree has them.
git diff --numstat "$(cat /tmp/spike-head)" | awk '$1 == "-" || $2 == "-" { print "binary:", $3; bad=1 } END { exit bad+0 }'
The spike does not prove prompt quality. It does not prove model quality. It proves whether the checkout is a contract. Multi-agent trees need one allowlist per agent. A shared list hides who drifted.
Free model access can be slower than a paid loop. Speed is out of scope. Do not convert this spike into a latency bake-off. Do not invent a winner from one run.
Who should not use this
Skip this spike if the work is a genuine repo-wide migration. Skip it if generated code is the product. Skip it if your VCS is not git. Skip it if reviewers already enforce file-level CODEOWNERS with no exceptions. Skip it if you cannot freeze HEAD for ninety minutes.
Do not use the gate as a people metric. Extra paths are a process smell. They are not a developer score. Do not paste vendor names into the kill note. The note is for the wrapper, not marketing.
What this spike refuses to claim
No quota numbers appear here. No hardware claims appear here. No permanent free-tier promise appears here. Availability can change. Re-read the product docs before you schedule a team ritual. The method survives that change. The host might not.
If you run the same spike on a free server, keep the allowlist and the kill note. That pair is the artifact. The rest is noise.
Top comments (0)