DEV Community

Harper Xu
Harper Xu

Posted on

Leave Apply on the Host

You should leave apply on your own host. A guest generator may propose the patch only. It should never own your local git worktree.

That split is the architecture you should review first. Generation can live on a free remote server. Landing the change stays on a machine you control.

A guest resembles a contractor waiting in the lobby. They may slide one drawing under the glass. They may not badge in and edit the live rack.

Seat the process before the files

The risky question is which process can update HEAD. A clever diff is still just text until something applies it. If the guest can commit, a bad run becomes history.

Picture three rooms joined by one narrow door. The host room holds the clone, the hooks, and the tests. The lobby holds one proposal file and nothing else.

The guest room holds the generator and its scratch disk. You do not back up that scratch disk. You do not treat it as a second source of truth.

You copy a narrow task into that lobby. The guest reads the bundle and writes one diff. Your host then decides if that diff may land.

A free server is useful only in the guest room. MonkeyCode's free model access and free server option can fill that seat. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

This draft does not state model names or quotas. It also skips hardware claims and duration claims. Those terms change, and this draft does not verify them.

The operator describes the project as open source. This article does not invent a license line or a repository URL. You should read the current project page before you rely on access.

A free option is a seat, not a promise of uptime. If the guest disappears, your host should still build. Design the door so an empty lobby is a boring Tuesday.

What you let through the door

You should send the task, the allowed paths, and short slices. You should not send your home directory or token cache. A full chat log is also the wrong parcel for this door.

The guest needs enough context to draft a diff. It does not need the keys to your other repos. Wider context makes a wider leak, even when generation is free.

Keep the export on the host, in a script you can read. The sample below is a proposal you can run locally. It does not call a remote API, and it is not a benchmark.

#!/usr/bin/env bash
# propose-export.sh - host-side proposal, not a recorded run
set -euo pipefail
TASK="${1:?task file}"
ALLOW="${2:?allowlist file}"
OUT="${3:-./.review/lobby/bundle.txt}"
mkdir -p "$(dirname "$OUT")"
{
  printf 'TASK\n'
  sed -n '1,80p' "$TASK"
  printf '\nALLOW\n'
  sed -n '1,200p' "$ALLOW"
  printf '\nSLICES\n'
  while IFS= read -r path; do
    [ -f "$path" ] || continue
    printf '\n--- %s\n' "$path"
    sed -n '1,160p' "$path"
  done < "$ALLOW"
} > "$OUT"
wc -c "$OUT"
Enter fullscreen mode Exit fullscreen mode

Read that byte count before you upload the bundle. If the file looks too wide, shrink the allowlist. Then export again and read the count a second time.

A free server does not make a wide bundle safer. It only makes generation against that bundle cheaper to start. Cheap generation is not the same as a contained bundle.

Name the files in the allowlist for this task only. Do not reuse last week's list out of habit. Three named files still describe a guest seat.

Opening the whole tree turns the guest into a tenant. That tenant can see more than the task required. You should feel that widening as a design change, not as a convenience.

How the host fails closed

The guest drops the proposal diff in the lobby. Your apply gate runs on the host, as you, on a detached worktree. If the patch is dirty, the main worktree never moves.

Think of the detached worktree as a scratch bench in the shop. You can clamp the drawing there and try the cut. You do not move the finished cabinet until the cut is clean.

#!/usr/bin/env bash
# apply-gate.sh - host-side proposal, unexecuted as a suite
set -euo pipefail
DIFF="${1:?proposal.diff}"
ALLOW="${2:?allowlist file}"
ROOT="$(git rev-parse --show-toplevel)"
WT="$(mktemp -d)"
cleanup() {
  git -C "$ROOT" worktree remove --force "$WT" 2>/dev/null || rm -rf "$WT"
}
trap cleanup EXIT
git -C "$ROOT" worktree add --detach "$WT" HEAD
python3 "$ROOT/scripts/path_gate.py" --diff "$DIFF" --allow "$ALLOW"
git -C "$WT" apply --check "$DIFF"
git -C "$WT" apply "$DIFF"
git -C "$WT" diff --stat HEAD
test -x "$ROOT/scripts/run_suite.sh"
"$ROOT/scripts/run_suite.sh" "$WT"
Enter fullscreen mode Exit fullscreen mode

The suite check fails closed when the runner script is missing. That is deliberate, so a quiet skip cannot look like a pass. Point the gate at your real suite before you use it on a branch.

The path gate is the lock on the shop door. It rejects a diff that touches a file outside the allowlist. It rejects a gate edit when the gate is missing from the allowlist.

#!/usr/bin/env python3
# path_gate.py - proposal checker, not a measured control.
import argparse
import pathlib
import sys

def touched(diff_text):
    paths = set()
    for line in diff_text.splitlines():
        if line.startswith('+++ b/') or line.startswith('--- a/'):
            raw = line.split(chr(9), 1)[0][6:]
            if raw != '/dev/null':
                paths.add(raw)
    return paths

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--diff', required=True)
    parser.add_argument('--allow', required=True)
    args = parser.parse_args()
    allow_path = pathlib.Path(args.allow)
    allow = {ln.strip() for ln in allow_path.read_text().splitlines() if ln.strip()}
    diff_text = pathlib.Path(args.diff).read_text()
    bad = sorted(touched(diff_text) - allow)
    if bad:
        print('rejected paths:', ', '.join(bad))
        return 2
    print('path gate passed')
    return 0

if __name__ == '__main__':
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode

This checker reads path headers, not a full diff parser. Renames and quoted paths can slip past a shallow read. Replace it before you trust the seat on a real repo.

Walk a bad patch through that checker before you trust the seat. Use a diff that edits a workflow while the allowlist names one file. You want exit code two, and you want your branch still clean.

You can stage that drill with two local files. No remote account is required for the drill. The point is the locked door, not the generator brand.

printf 'src/app.py\n' > /tmp/allow.txt
cat > /tmp/bad.diff << 'EOF'
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1 +1 @@
-name: ci
+name: opened
EOF
python3 scripts/path_gate.py --diff /tmp/bad.diff --allow /tmp/allow.txt
echo exit:$?
git status --short
Enter fullscreen mode Exit fullscreen mode

Run the drill on a throwaway clone, not on your main checkout. The checker only reads the diff and the allowlist. It should not need network, credentials, or a running guest.

Record the exit code next to the task name in your notes. A passing drill today does not certify next month's server image. You re-run the drill when the gate script changes.

Network loss is a separate break from a bad diff. If the guest server dies mid-draft, the lobby stays empty or half written. The trap removes the temporary worktree, and your HEAD stays put.

A half-written diff should fail the apply check. That failure is a closed door, not an incident. You delete the lobby file and you ask for a fresh proposal.

What you would change next

This seat is a review sketch, not a finished platform. The next change is to split the test user from the apply user. The runner should read the worktree and write logs only.

A later host step may commit after those logs look right. That step should be a different script, with a different user. One process should not both grade the work and file the grade.

You should also stop treating one green local suite as enough. A second witness should apply the same diff on a clean checkout. Your existing CI job can be that witness without the guest disk.

If the two results disagree, you leave the patch in the lobby. You do not merge to soothe a deadline. Disagreement is a signal that the seat is still too wide.

Shrink the allowlist every time the task changes. A guest that may edit three named files is still a guest. A guest that may edit the repo has become a tenant.

Tenants need an owner, a retention story, and a revoke path. A free server option is a weak place to invent those controls later. Put the revoke on the host door, where you already hold the keys.

Do not use this seating if the tree holds production secrets. Do not use it when policy forbids sending source off the machine. Do not use it if you need a signed retention story.

Skip the seat when that story is not yet published. In those cases, keep the generator local or skip generation. A free remote seat cannot outrank a written rule.

In that case the lobby door should stay shut. Local tools can still draft inside the host room. You lose the free guest, and you keep the cabinet.

If your host gate already rejects bad paths, a free guest can draft. Read the current access terms on the project site before you depend on them. Then keep apply on the host, beside the hooks you already trust.

Top comments (0)