Did your agent ask for the region?
Or did it silently pick us-east-1 anyway?
I keep catching this in free-tier runs.
Free models guess when your prompt has holes.
Free servers then execute those guesses for real.
That pairing is useful. It is also sharp.
This is not another latency FAQ.
This is not a git-status lecture either.
It is about blanks the model fills without asking.
Why these myths keep shipping
Agents sound careful in the chat log.
The command line is where the story changes.
Missing flags become popular defaults. Fast.
I treat every proposed command as a patch.
Unset fields are bugs, not cleverness.
Would you merge a PR with empty required keys?
Myth 1: Missing inputs make the agent stop
Claim: If a value is absent, it will ask.
What I actually see: it invents a common default.
Corrected model: silence is a fill-in, not a halt.
Watch deploy snippets like this one.
# Proposed by an agent. Looks complete. It is not.
aws s3 cp ./dist s3://my-bucket/app --recursive
kubectl apply -f k8s/
docker push my-org/api:latest
Which region? Which context? Which registry login?
None of those questions appeared in chat.
The model still emitted a runnable line.
If you did not name the value, assume a guess.
Ask the agent to print unknowns as TODO.
Refuse to run lines that still contain TODO.
Myth 2: The free server has my laptop identity
Claim: I am logged in, so the box is too.
What I actually see: empty profiles and expired tokens.
Corrected model: identity is injected, never inherited.
Your laptop has ~/.aws, gh auth, and kubeconfigs.
A free server starts without that private clutter.
The model will still write commands that need it.
# Run these on the box, not in the chat log.
whoami
env | grep -E 'AWS_|GH_|KUBE' || true
aws sts get-caller-identity || echo "no aws identity"
gh auth status || echo "no gh auth"
kubectl config current-context || echo "no kube context"
Did any command succeed without you passing secrets?
Then you are not looking at your laptop.
You are looking at a stranger with your script.
I copy only scoped, short-lived credentials.
I never paste a long-lived cloud key into chat.
If the gate cannot see an identity, it must stop.
Myth 3: Confident prose means the flags exist
Claim: A firm plan implies real, safe flags.
What I actually see: helper flags that skip checks.
Corrected model: confidence is style, not evidence.
The model wants the story to end cleanly.
So it reaches for --force, --no-verify, --yes.
Those flags are not proof. They are accelerants.
# High-confidence, high-regret patterns
git commit --amend --no-verify -m "fix ci"
git push --force origin main
npm publish --access public -y
chmod -R 777 ./data
curl -k https://internal.example/health
Would you type those flags on a Friday?
Then do not let a free model type them either.
I require an allow-list for every dangerous switch.
Myth 4: Exit code 0 proves the default was right
Claim: It succeeded, so the guess was correct.
What I actually see: success on a weaker default.
Corrected model: green can mean "unconstrained".
This is not the HTTP-200 myth.
The process really ran. The default still lied.
npm install without a lockfile still exits 0.
# Same exit code. Different worlds.
npm ci # respects the lockfile
npm install # may drift
pip install -r reqs.txt
pip install -r reqs.txt --require-hashes
docker build -t api:dev .
docker build --pull --no-cache -t api:dev .
Did the tool use the lock you thought it used?
Print the resolved set and hash it yourself.
Compare that digest to CI, not to the chat.
# Proposal: pin, then fingerprint.
npm ci
sha256sum package-lock.json
python - <<'PY'
from pathlib import Path
import hashlib
p = Path("package-lock.json")
print(hashlib.sha256(p.read_bytes()).hexdigest())
PY
Success without a pin is only a lucky path.
I keep the pin in git. I hash it in the gate.
No hash match, no next step.
Myth 5: The model will refuse a destructive default
Claim: Safety training will block the bad flag.
What I actually see: latest, DROP, and --force.
Corrected model: refusal is a policy you own.
Models hedge in paragraphs.
They still emit a shell one-liner under pressure.
"Be careful" in prose does not bind the command.
-- Proposed "cleanup" that needs a human brake
DELETE FROM sessions;
DROP TABLE IF EXISTS events_backup;
# Image tags that hide which bits you shipped
docker pull my-org/api:latest
kubectl set image deploy/api api=my-org/api:latest
Who tagged latest? When? From which commit?
If you cannot answer, you do not have a release.
You have a moving pointer with a friendly name.
The corrected mental model
Stop asking whether the agent "understood."
Ask which required field it left blank.
Then decide whether a default may exist at all.
I use three buckets only:
- Must be explicit. Region, account, context, ref.
- May default, if pinned. Lockfiles, image digests.
- Never default. Force-push, skip-hooks, world-writable.
If a field is in bucket one or three, block it.
Do not debate the model's tone. Read the argv.
Artifact: an assumption gate you can run
I do not parse English promises.
I scan the proposed command text before exec.
The script below is a gate, not a shell parser.
Label this as a proposal you should read first.
It is a denylist plus required tokens.
It is not a full AST for bash.
#!/usr/bin/env python3
"""assumption_gate.py — fail closed on silent defaults."""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
DENY = [
re.compile(r"\b--force\b"),
re.compile(r"\b--no-verify\b"),
re.compile(r"\b--amend\b"),
re.compile(r"\bchmod\s+-R\s+777\b"),
re.compile(r"\bDROP\s+TABLE\b", re.I),
re.compile(r":latest\b"),
re.compile(r"\bnpm\s+install\b(?!.*package-lock)"),
]
REQUIRED_ANY = {
"aws": [re.compile(r"--region\s+\S+"), re.compile(r"AWS_REGION=")],
"kubectl": [re.compile(r"--context\s+\S+")],
"docker push": [re.compile(r"@sha256:[0-9a-f]{64}")],
}
def load_allow(path: Path) -> set[str]:
if not path.exists():
return set()
return {line.strip() for line in path.read_text().splitlines() if line.strip() and not line.startswith("#")}
def check(cmd: str, allow: set[str]) -> list[str]:
hits: list[str] = []
if cmd.strip() in allow:
return hits
for pat in DENY:
if pat.search(cmd):
hits.append(f"denied pattern: {pat.pattern}")
for needle, pats in REQUIRED_ANY.items():
if needle in cmd and not any(p.search(cmd) for p in pats):
hits.append(f"missing explicit field for {needle}")
return hits
def main() -> int:
if len(sys.argv) < 2:
print("usage: assumption_gate.py COMMAND_FILE", file=sys.stderr)
return 2
allow = load_allow(Path("allowed_commands.txt"))
cmd = Path(sys.argv[1]).read_text()
hits = check(cmd, allow)
print(json.dumps({"command": cmd.strip(), "violations": hits}, indent=2))
return 1 if hits else 0
if __name__ == "__main__":
raise SystemExit(main())
Keep the allow-list tiny and boring.
# allowed_commands.txt — exact lines only
npm ci
python assumption_gate.py proposed.sh
Wrap the free-server run so the model cannot skip it.
#!/usr/bin/env bash
set -euo pipefail
# proposed.sh is the agent's command dump, one block.
python3 assumption_gate.py proposed.sh
# Only then exec. Still use a human for prod.
bash proposed.sh
Decision table I actually use
| Signal in the command | Myth it feeds | Gate action |
|---|---|---|
No --region / no context |
Myth 1 and 2 | Block |
--force / --no-verify
|
Myth 3 and 5 | Block unless exact allow |
npm install without lock |
Myth 4 | Rewrite to npm ci or block |
:latest |
Myth 5 | Require digest |
| Prose says "careful" | Myth 3 | Ignore prose |
| Exit code 0, no hash | Myth 4 | Fail the job |
Print the table in the PR if you must.
Do not paste the chat transcript as proof.
The table is the review surface. Keep it short.
A 20-minute drill on a free box
I iterate the gate where guesses are cheap.
MonkeyCode's free model access and free server option are enough for that loop.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Here is the drill, labeled as a procedure, not a benchmark.
- Ask the model for a deploy script from a vague prompt.
- Save the script as
proposed.sh. Do not run it. - Run
assumption_gate.pyagainst that file. - Tighten the prompt: require region, digest, lockfile.
- Repeat until the gate exits 0, then read the script.
Vague prompt I use on purpose:
Ship the API. Use the usual defaults. Keep it simple.
Tighter prompt after the gate fails:
Write commands only. No defaults.
Require: AWS region, kube context, image sha256,
and npm ci. If a value is unknown, emit TODO and stop.
Did the second dump still hide a flag?
Then your prompt is not the control plane.
The gate is. Keep both.
Limitations, said plainly
This gate is regex over text.
It will miss quoted tricks and subshells.
It will also flag safe commands that look scary.
Do not use this as production IAM.
Do not use this as a substitute for code review.
Do not use this if you need a real policy engine.
Skip this approach when:
- You already have a signed, human-reviewed runbook.
- Your shell is too dynamic for line-level checks.
- You cannot isolate secrets from the prompt log.
- You expected the free server to be your laptop.
I also do not claim model names, quotas, or hardware here.
Those change. The argv does not lie as often.
Verify current product limits on the vendor's own docs.
What I want you to take
Ask one question before you let the box run:
which required field did the agent leave blank?
If you cannot point to it, you already have a default.
Steal the gate. Break it. Add your own deny patterns.
Then make the free model argue with the exit code, not with you.
Top comments (0)