payloadcms / payloadcms/payload
payload run reports success in four distinct failure modes, one of which dev-pushes to a remote database
@r1tsuu is already working on this.
Since Sep 11, 2026.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
payload run reports success in four distinct failure modes, one of which writes to a remote database.
Verified against payload@3.87.1 and @payloadcms/db-postgres@3.87.1 by reading the published dist. Reporting these together because they share a root cause — the CLI wrapper does not preserve the semantics of the script it runs — and because together they make payload run unsafe as a CI or build gate. We used it as a pre-deploy migration gate, and the gate could never have failed.
1. The exit code is discarded
dist/bin/index.js ends the non-cron branch with an unconditional process.exit(0) after runBinScript returns:
const { payload } = await runBinScript({ args, script })
if (payload) { await payload.destroy() }
process.exit(0)
A script that sets process.exitCode = 1 — the documented, non-throwing way to fail — therefore reports success to the shell. A thrown error is the only failure signal that propagates (via the catch inside runBinScript, which calls process.exit(1)).
Repro: payload run ./fail.ts where fail.ts is process.exitCode = 1; echo $? prints 0.
Suggested fix: propagate a non-zero process.exitCode, or document prominently that only throw fails.
2. A floating promise is abandoned
payload run script.ts awaits module evaluation and then exits, so a trailing void main() never completes. The script prints nothing and exits 0 — indistinguishable from success. Top-level await is the workaround; a docs line would save the discovery.
3. CLI flags never reach the script
process.argv is rebuilt from minimist's positional arguments only:
process.argv = [process.argv[0], process.argv[1], ...args._.slice(2)]
So --pre is consumed by the CLI and process.argv.includes('--pre') inside the script is always false, silently.
Suggested fix: forward unrecognised flags, or document that arguments must be passed via the environment.
4. It performs a dev schema push against whatever DATABASE_URL names
db-postgres/dist/connect.js:110 guards dev push with three conditions:
if (process.env.NODE_ENV !== 'production' &&
process.env.PAYLOAD_MIGRATING !== 'true' &&
this.push !== false) {
await pushDevSchema(this)
}
Two of those are set for you where it matters — bin/migrate.js sets PAYLOAD_MIGRATING='true', and next build sets NODE_ENV=production. payload run sets neither: bin/index.js sets only DISABLE_PAYLOAD_HMR, and bin/loadEnv.js merely reads NODE_ENV. So for an ad-hoc payload run ./seed.ts with NODE_ENV unset — the default in a plain shell — all three conditions pass and the adapter dev-pushes against whatever DATABASE_URL resolves to, including a production database. It then stamps payload_migrations = (dev, batch -1), and the next deploy's migration gate refuses to run.
Two things make this expensive rather than merely surprising:
migrate:statusdoes not show the(dev, -1)row, so the tool you would reach for to diagnose a refused deploy does not reveal the cause.- Combined with (1), a guard script written to prevent this cannot fail the build, because
payload rundiscards its exit code.
Ask: NODE_ENV is a weak proxy for "is this database disposable". Either default push to false for payload run (a one-off script is not a dev server), or refuse a dev push against a non-local host unless explicitly opted in, or at minimum log a warning naming the host before pushing. Today the safe configuration is something each user discovers only after being bitten.
Happy to open a PR for any of these if the direction is agreed — (1) and (3) look mechanical; (4) is a behavioural default you may want to choose differently than we would.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.