Common pitfalls
Anti-patterns that quietly defeat scale-to-zero and agent safety — and how to avoid them.
These patterns don't error. They pass CI, they look reasonable in a diff, and they quietly cancel a guarantee you're paying for — scale-to-zero savings or agent safety. Each one below: what happens, why it hurts, and the fix.
Keep-alive code defeats scale-to-zero
An agent "helpfully" adds code that touches the database on a timer. Every periodic query counts as activity and resets the suspend timer, so an endpoint you designed to scale to zero never sleeps — and idle stops being free.
It arrives in several shapes. A frontend or backend heartbeat:
// runs forever, holds the compute awake forever
setInterval(async () => {
await pool.query("SELECT 1"); // "health check"
}, 30_000);An external uptime monitor pointed at an endpoint whose handler happens to query the DB — so every probe wakes compute:
app.get("/health", async (_req, res) => {
await pool.query("SELECT 1"); // monitored every 60s → never idle
res.send("ok");
});Or connection-pool settings that keep a live connection with validation
queries — a min above zero, or a serverless "warmer":
const pool = new Pool({
min: 2, // keeps ≥2 connections open + validated
// a validation/keep-alive query on each idle connection resets the timer
});Fix: audit agent-written diffs for periodic database access. Keep liveness
checks at the HTTP layer — probe the app, not the DB — and let the pool drain
to zero (min: 0). The console compute view shows why an endpoint isn't
scaling to zero, which surfaces a stray heartbeat fast.
Handing the agent main credentials short-circuits ASCC
Giving an agent the main-branch connection string — or letting it run keon
logged in as your own user — bypasses everything ASCC enforces: sandbox
isolation, LEASH budgets, LINT and BLAST checks, promote gates, attribution,
and undo anchors. The agent is now writing production directly, and none of the
loop that makes agent changes safe ever runs.
The agent should never hold a credential that can write main. Promotion is
server-side by design — cp is the only writer to production.
Fix: mint an agent-scoped API key (capability = agent, scope = a
project, short expiry) at Settings → API keys, hand the agent sandbox
connection strings only, and remove stronger credentials from its environment
(no full DATABASE_URL, no ~/.pgpass, no logged-in keon session in the same
shell). See Hand an agent a scoped credential and the
SCOPE section of Guardrails.
Retry storms around wake-up
The first connection after a suspend has to wake compute, so it's slower than a warm one. An agent that sees a single slow connect may "fix" it — with aggressive client timeouts and retry loops that hammer the endpoint, or by adding a keep-alive (pitfall #1) so it never has to wake at all. Both trade the savings away to paper over a one-time cost.
Fix: set a client connect_timeout around 10s and be patient on the first
connect. Cold start is sub-second same-region once warm paths are in play — one
slightly-slow connect is expected, not a fault to engineer around.
postgresql://…?connect_timeout=10Credentials committed to code
Agents hard-code connection strings and API keys into source and .env files
that then reach git. Leaked keys get harvested within hours of hitting a public
(or later-public) repo — the window is measured in hours, not days.
Fix: inject secrets via the environment, never commit them. Keep .env
files out of git. On any leak, rotate immediately from Settings → API keys
— revoke the exposed key and mint a fresh one.
Direct DDL on main bypasses the promote loop
Running a migration straight against main skips the capture that makes a
change reversible and auditable. You lose the emitted up/down migration (EMIT),
the signed ledger entry (LEDGER), and the undo anchor (UNDO). The schema
changed, but there's no record the platform can replay or roll back.
Fix: sandbox → promote, even for a "trivial" schema change. The promote loop
is what produces the migration, the ledger entry, and the anchor; a direct
ALTER produces none of them.
Writes inside a rolled-back transaction still land
Capture records the statements the agent runs — it has no
transaction-boundary awareness. Statements executed inside a BEGIN … ROLLBACK
are still captured, and if that sandbox is promoted, they are replayed and
applied. A ROLLBACK in the sandbox does not un-capture them.
BEGIN;
UPDATE users SET plan = 'pro'; -- captured
ROLLBACK; -- does NOT remove it from the captureFix: don't rely on ROLLBACK inside a sandbox to "undo" exploratory work.
If you want work gone, discard the sandbox — that's the disposal path capture
respects.
Parking work in a sandbox past its budget or TTL
Sandboxes are ephemeral. When a sandbox exhausts its LEASH budget it is
auto-discarded (status=discarded), and any un-promoted work in it is lost.
Treating a sandbox as a long-lived branch you'll "get back to" is how a day's
changes vanish.
Fix: promote it, or re-capture the work in a fresh sandbox. Treat sandboxes as disposable by default — the durable artifact is the promote, not the sandbox.
Misreading the guarantees
Three specific traps in what the checks actually promise:
-
The blast-radius dry-run is advisory. It measures what a promote would do — real locks, durations, row counts — but it never gates the promote. A clean dry-run is information, not a green light; and whether a dry-run is required before promote is POLICY's decision, not the dry-run's.
-
Approve can't override a policy block. A human Approve can never wave through a policy
blockor an out-of-lane statement. The only way past a block is an audited policy change — there is no manual override switch, by design. -
A masked fork masks values, not shape. On a masked fork the values are masked, so data-dependent DML the agent writes against those masked values won't match the real rows on the parent at replay. Schema-lane work is value-independent and replays faithfully; value-dependent DML developed against masked data does not.
Masked forks
Give an agent (or a human) prod's shape and none of its PII — masking policies, the built-in function library, and how masked branches and sandboxes stay sealed until the mask commits.
Hand an agent a scoped credential
Provision a capability-, project-, and expiry-scoped API key and run an AI agent against your database safely.