DEV Community

Jordan Huang
Jordan Huang

Posted on

FAQ: Your Free Server Closed. What Actually Shipped?

You closed the tab. Did any patch survive?

I keep hearing the same claims. An agent ran on a free box. The prompt said done. Someone treats that as shipped work. That is a story, not a receipt.

This FAQ is about promotion. How does work leave a scratch session? What still counts after the process dies?

I use first-person here as a reviewer. I am not claiming a production outage. Every command below is a proposed check. Run it. Do not trust my memory.

Why this keeps happening

Agents now live on rented shells. Your laptop is not the only cwd. That is useful. It is also a trap.

A session can look complete and still own nothing durable. Git never saw the files. CI never saw the SHA. Your laptop never fetched the branch.

I sometimes park that loop on MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach. It offers free model access and a free server option. That is all I will claim. No model names. No quotas. No hardware story.

The product is not the point. The receipt is the point.

Q1. The remote prompt said success. Is that a merge receipt?

No. A sentence is not git.

Ask a ruder question. What refs exist after the praise?

git rev-parse --abbrev-ref HEAD
git rev-parse HEAD
git status --porcelain=v1
git log -1 --format='%H %D %s'
Enter fullscreen mode Exit fullscreen mode

If HEAD is detached, you do not have a branch. If porcelain is empty, maybe the tree is clean. Maybe you are in the wrong clone.

Corrected model: success text is narration. A merge receipt is a SHA on a named ref that origin also has.

git rev-parse HEAD
git rev-parse @{u} 2>/dev/null || echo 'no upstream'
git cherry -v 2>/dev/null || true
Enter fullscreen mode Exit fullscreen mode

No upstream? Nothing shipped. Stop talking about done.

Q2. Local HEAD matches origin. Am I finished?

Matching SHAs can still hide a dirty tree. People skip porcelain. Then they ship a lie.

Check three planes, not one hash.

  1. Committed tree: git rev-parse HEAD
  2. Index: git diff --cached --stat
  3. Worktree: git diff --stat plus untracked files
echo "HEAD $(git rev-parse HEAD)"
echo "INDEX"
git diff --cached --name-only
echo "WORKTREE"
git diff --name-only
echo "UNTRACKED"
git ls-files --others --exclude-standard
Enter fullscreen mode Exit fullscreen mode

Same SHA, dirty tree? You verified a commit, not the session. The agent may have written files after the commit. Those files die with the box.

Corrected model: a SHA is a photograph. A dirty tree is a room you have not photographed.

Q3. Untracked output on the free box counts as delivered?

Counts for whom? The disk that will vanish?

Agents love /tmp, ./out, and pytest cache folders. They print paths. Humans screenshot paths. Nobody adds the path to git.

Proposed sniff for junk that feels like a deliverable:

find . -maxdepth 3 \
  \( -name '*.xml' -o -name '*.html' -o -name 'coverage*' \
     -o -name 'dist' -o -name 'build' \) \
  -not -path './.git/*'
Enter fullscreen mode Exit fullscreen mode

Label this as a hunt, not a linter. Tune the names. Then decide, file by file:

  • Commit it, because reviewers need it.
  • Upload it, because CI already rebuilds it.
  • Drop it, because it is noise.

Corrected model: if git cannot clone it, you did not deliver it. A free server is not an artifact store.

Q4. I ran it on the free server. Will CI match?

Maybe the tests passed. Which Python? Which locale? Which PATH?

Do not argue. Dump the runner identity into a file you actually keep.

{
  echo "date_utc $(date -u +%Y-%m-%dT%H:%M:%SZ)"
  echo "pwd $(pwd)"
  echo "uid $(id -u) gid $(id -g)"
  echo "uname $(uname -a)"
  echo "shell $SHELL"
  echo "path $PATH"
  command -v python3 && python3 -c 'import sys,locale; print(sys.version); print(locale.getlocale())'
  command -v node && node -v
  git --version
} > session_identity.txt
Enter fullscreen mode Exit fullscreen mode

Commit that file only if your team wants runner forensics. Otherwise keep it next to the PR comment. Compare it to CI logs later.

PATH myths die here. Your laptop uses pyenv. The box uses distro Python. Both print green. They are not the same experiment.

Corrected model: a passing command is bound to an identity dump. No dump, no comparison.

Q5. Closing the session archives the evidence?

Closing a tab is not git push. It is not tar. It is not CI cache.

I treat session end like a power loss. Anything not pushed is gone. That includes:

  • rebase state in .git
  • stash entries nobody named
  • hook output
  • .env the agent invented
  • test databases in /tmp

Proposed last-minute grab, still unexecuted until you run it:

# proposed: run before you kill the session
mkdir -p "$HOME/receipts"
OUT="$HOME/receipts/receipt-$(date -u +%Y%m%dT%H%M%SZ).txt"
{
  git remote -v
  git status --porcelain=v1 -b
  git stash list
  git diff --stat
  git log -5 --oneline --decorate
} > "$OUT"
echo "wrote $OUT"
Enter fullscreen mode Exit fullscreen mode

If the free server deletes $HOME on stop, this file dies too. Then the only archive is a push. Push or lose it. That is the whole FAQ.

Corrected model: archive means another machine can fetch it. Chat history is not that machine.

Artifact: a session receipt you can diff

Here is a small proposed script. I have not benchmarked it. It writes a receipt. You copy that receipt off the box. Then you diff two receipts.

#!/usr/bin/env bash
# session_receipt.sh — proposed helper, not a product claim
set -euo pipefail

root=$(git rev-parse --show-toplevel 2>/dev/null || true)
if [[ -z "$root" ]]; then
  echo "not a git worktree" >&2
  exit 2
fi
cd "$root"

receipt=${1:-/tmp/session-receipt.json}

python3 - <<'PY' "$receipt"
import json, os, subprocess, sys, time

def sh(args):
    p = subprocess.run(args, text=True, capture_output=True)
    return p.returncode, (p.stdout or "").rstrip("\n"), (p.stderr or "").rstrip("\n")

def out(args):
    code, stdout, _ = sh(args)
    return stdout if code == 0 else ""

receipt_path = sys.argv[1]
head = out(["git", "rev-parse", "HEAD"])
branch = out(["git", "rev-parse", "--abbrev-ref", "HEAD"])
upstream = out(["git", "rev-parse", "@{u}"])
porcelain = out(["git", "status", "--porcelain=v1"])
untracked = out(["git", "ls-files", "--others", "--exclude-standard"])

payload = {
    "generated_at_unix": int(time.time()),
    "cwd": os.getcwd(),
    "head": head,
    "branch": branch,
    "upstream": upstream or None,
    "dirty": bool(porcelain),
    "porcelain": porcelain.splitlines() if porcelain else [],
    "untracked": untracked.splitlines() if untracked else [],
    "head_subject": out(["git", "log", "-1", "--format=%s"]),
}
with open(receipt_path, "w", encoding="utf-8") as f:
    json.dump(payload, f, indent=2)
    f.write("\n")
print(receipt_path)
PY
Enter fullscreen mode Exit fullscreen mode

Usage I actually want in a PR comment:

chmod +x session_receipt.sh
./session_receipt.sh /tmp/session-receipt.json
# then copy the json off the box before logout
Enter fullscreen mode Exit fullscreen mode

Diff two receipts without drama:

python3 - <<'PY'
import json, sys
a = json.load(open(sys.argv[1]))
b = json.load(open(sys.argv[2]))
keys = ["head", "branch", "upstream", "dirty"]
for k in keys:
    if a.get(k) != b.get(k):
        print(f"MISMATCH {k}: {a.get(k)!r} vs {b.get(k)!r}")
print("untracked_a", a.get("untracked"))
print("untracked_b", b.get("untracked"))
PY
# python3 compare.py laptop.json server.json
Enter fullscreen mode Exit fullscreen mode

If head matches and dirty is false on both, you may talk about shipping. If untracked lists differ, you are still arguing about files that git does not know.

Decision table: what still counts

Claim you heard Evidence that would support it If evidence is missing
"The agent finished." Named branch, clean porcelain, pushed SHA Treat as a draft on a dying disk
"Tests ran." CI log for that SHA, not session stdout Re-run on a runner you control
"Artifact is included." Path in git ls-files Rebuild or drop the claim
"Same as my laptop." Two receipts, matching head and identity dump You compared vibes
"We archived the session." Fetchable URL or git remote You archived a feeling

Print that table next to the PR. It kills most arguments in one scan.

A workflow I will actually follow

Short loop. No theatre.

  1. Start the agent on the free server if you want cheap iteration.
  2. Force every "done" through session_receipt.sh.
  3. Push a named branch before you close anything.
  4. Copy the receipt to your laptop.
  5. Run the same receipt locally.
  6. Only then paste a SHA into review.

Skip a step and you are demoing a ghost.

Want a brutal extra? Delete the remote working tree in your head the moment you log out. If the branch is not on origin, it never existed.

git fetch origin
git rev-parse origin/your-branch
# fail here? your free server did not ship
Enter fullscreen mode Exit fullscreen mode

Limitations

This receipt is git-shaped. It will not catch data you wrote to a hosted database. It will not catch secrets in shell history. It will not freeze container layers.

It also will not make two Pythons identical. Identity dumps explain mismatch. They do not fix mismatch.

Do not commit receipts that leak host names you cannot share. Redact. Or keep them in the PR, not the repo.

The script uses python3 and git. If either is missing, the receipt is incomplete. That failure is useful. It means the box is not the runner you imagined.

Who should not use this

Skip this workflow if you already have locked-down CI as the only writer. Your agents should not push. Humans merge from CI green only.

Skip it if the free server may touch production credentials. A scratch box is a scratch box. Do not launder secrets through it.

Skip it if you need a legal archive. JSON in /tmp is not a records system.

And skip it if you wanted a model leaderboard. I did not provide one. I will not invent names or scores.

What I want you to repeat instead

Ask one question at logout. Can another machine fetch this SHA?

If yes, you shipped a candidate. If no, you rented a story. Free model access does not change that physics. A free server does not change that physics.

When a teammate pastes a chat ending in "all tests passed," ask for porcelain. Ask for origin. Ask for the receipt file. That is the whole job.

If you run the receipt script, paste the JSON. Do not paste the pep talk.

Top comments (0)