cd /news/ai-agents/the-scratch-server-went-green-you-st… · home topics ai-agents article
[ARTICLE · art-138357] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

The Scratch Server Went Green. You Still Need a Local Gate.

A developer published a set of local CI gate recipes and policy files designed to stop teams from trusting free agent sandboxes as merge queues. The writeup argues that green output from an untrusted scratch server is not a signed provenance record, and that agent patches must be replayed on a controlled runner with local tests, secret scanning, and an executable deny-list policy before merging. It includes bash, Python, and YAML examples for enforcing those checks.

by read7 min views2 publishedSep 23, 2026

A free agent box is not your merge queue.

You still need a local, boring, repeatable gate.

Would you ship code from a laptop you do not own?

That is the whole article, stated up front.

The rest is a catalog of ways teams skip it.

I wrote the checks as commands you can run.

Cheap inference changed the cost of a first draft.

It did not change who owns the blast radius.

Did your policy file follow the model, or stay in chat?

I keep six failures on a short review list.

Each failure looks like speed on a busy afternoon.

Each failure still ships as untracked engineering debt.

The scratch server printed a passing test line.

The pull request skips CI because the agent ran tests.

Main now trusts a machine you do not control.

People confuse a demo host with a trusted runner.

A free server is a sandbox, not an attesting builder.

Green output is not a signed provenance record.

Replay every agent patch on your own runner.

Fail the merge when local tests never executed.

Keep the remote box for drafts, not releases.

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

if [[ ! -f .agent/origin.json ]]; then
  echo "missing .agent/origin.json" >&2
  exit 1
fi

replayed="$(jq -r '.replayed_locally' .agent/origin.json)"
if [[ "${replayed}" != "true" ]]; then
  echo "patch was not replayed locally" >&2
  exit 1
fi

if [[ "${CI:-}" != "true" ]]; then
  echo "refusing to bless a merge outside CI" >&2
  exit 1
fi

git diff --check
pytest -q

Did those agent tests even use your lockfile?

If you cannot answer, you cannot merge yet.

Someone pasted a dotenv file into the agent thread.

The free server now holds a production token.

Rotation starts only after the screenshot already leaked.

Convenience still beats threat modeling under time pressure.

A shared scratch box is not your secret store.

Free compute does not include any free confidentiality.

Redact first, then inject secrets only in local CI.

I keep a dumb scanner in the pre-push path.

It looks ugly, and it is enough for drafts.

from __future__ import annotations

import re
import sys
from pathlib import Path

PATTERNS = [
    re.compile(r"AKIA[0-9A-Z]{16}"),
    re.compile(r"-----BEGIN (RSA |OPENSSH )?PRIVATE KEY-----"),
    re.compile(r"(?i)(api[_-]?key|secret)\s*=\s*\S+"),
]

def main(paths: list[str]) -> int:
    failed = False
    for raw in paths:
        path = Path(raw)
        if not path.is_file():
            continue
        text = path.read_text(errors="ignore")
        for pat in PATTERNS:
            if pat.search(text):
                print(f"secret-like match in {path}")
                failed = True
    return 1 if failed else 0

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
git diff --name-only --cached -z | xargs -0 -r python secret_scan.py

Would you paste that token into a hallway laptop?

Then do not paste it into a free agent server.

The system prompt says never touch production files.

The agent still edits the deploy workflow anyway.

Nobody encoded the rule as a failing check.

Natural language feels like a real control plane.

Models drift over sessions, while files stay put.

A policy that cannot fail CI is a wish.

Put deny paths in an executable policy file.

I like a tiny allow and deny list in-repo.

The agent may read it, but CI must enforce it.

deny_globs:
  - ".github/workflows/**"
  - "infra/prod/**"
  - "**/*.env"
  - "**/credentials.json"
allow_globs:
  - "src/**"
  - "tests/**"
  - "docs/**"
python
from fnmatch import fnmatch
from pathlib import Path
import subprocess
import sys
import yaml

policy = yaml.safe_load(Path(".agent/policy.yml").read_text())
diff = subprocess.check_output(
    ["git", "diff", "--name-only", "origin/main...HEAD"],
    text=True,
).splitlines()

blocked = False
for path in diff:
    if any(fnmatch(path, glob) for glob in policy["deny_globs"]):
        print(f"denied path: {path}")
        blocked = True

sys.exit(1 if blocked else 0)

If the check is optional, the anti-pattern remains.

Make it blocking, then argue exceptions in the PR.

One long agent thread spans three private repositories.

Context from repo A leaks into a patch for B.

The summary sounds confident, but the imports are wrong.

Sessions are cheap, and isolation is not the default.

A free server often outlives your actual attention.

Yesterday's stack trace becomes today's invented client API.

Use one repo, one worktree, and one short session.

Destroy the thread when the branch finally has a name.

Record the origin file before you close the tab.

{
  "generated_at": "2026-09-23T12:00:00Z",
  "repo": "payments-api",
  "base_sha": "REPLACE_WITH_LOCAL_SHA",
  "session": "scratch-only",
  "replayed_locally": false
}

Set replayed_locally true only after local CI passes.

Anything else is still a draft, not a candidate.

Someone says the model needs a realistic payload.

A customer export then lands on the scratch server.

The model is free, but that dataset is not.

Realism gets confused with actual processing permission here.

Free inference still does not grant data-processing rights.

A redacted fixture is slower, and it is legal.

Build a tiny fixture set inside the repository.

I would rather ship boring JSON than a real dump.

Name the file fake, and then keep it fake.

{
  "order_id": "ord_test_001",
  "amount_cents": 1999,
  "region": "lab",
  "email": "user@example.test"
}
if git diff --name-only | grep -E '(customers|pii|prod-dump)'; then
  echo "prod-shaped filename in the diff" >&2
  exit 1
fi

Do you have a deletion ticket for that upload?

If not, you already lost the data conversation.

The patch arrived as a blob from the chat.

Nobody can regenerate it from a recorded prompt.

Reviewers argue with a screenshot, not a command.

Generation starts to feel like real authorship too quickly.

Authorship without a replay path is just folklore.

Folklore does not bisect when production later breaks.

Export a patch file, then apply it locally.

Run the same tests your CI will run tomorrow.

If apply fails, the remote box lied about the tree.

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

base="$(jq -r '.base_sha' .agent/origin.json)"
git checkout -B "replay/${USER}" "${base}"
git apply --check /tmp/agent.patch
git apply /tmp/agent.patch

tmp="$(mktemp)"
jq '.replayed_locally = true' .agent/origin.json > "${tmp}"
mv "${tmp}" .agent/origin.json

pytest -q

Can a stranger reproduce this without the original chat?

If they cannot, you do not have a change.

You only have a vibe from a closed tab.

I print this table near the merge button.

I still miss a row when the review is rushed.

The table is the review, not the model output.

Signal Merge Why
Remote tests only No Host is not your runner
Secrets in the thread No Rotate first, then rewrite
Policy only in the prompt No Encode a failing check
Mixed-repo session No Split and regenerate
Real customer payload No Replace with fixtures
Local apply and CI green Yes Now it is your patch

I still want a scratch pad for ugly first drafts.

Throwaway refactors do not deserve a paid cluster.

They also do not deserve a silent path to main.

MonkeyCode offers free model access and a free server option.

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

I treat that option as a disposable editor, nothing more.

The workflow stays boring on purpose for a reason.

Drafts can be remote without becoming merge artifacts.

Merges cannot be remote without a local replay.

Skip step one if your code cannot leave the building.

Skip none of the later steps if it can.

This catalog will not make a model honest.

It only makes a dishonest merge much harder.

That is the actual job of these gates.

Do not use a free scratch server in these cases.

I also will not claim tokens, uptime, or model names.

Those numbers go stale by the next product page.

Trust the commands you can rerun after lunch.

Clone the repo, apply the patch, scan, then test.

If a step fails, the agent did not finish.

The chat window is not a merge witness.

Want a disposable box for that first ugly draft?

Use a free server, then run the local gate at home.

── more in #ai-agents 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-scratch-server-w…] indexed:0 read:7min 2026-09-23 ·