Skip to content
Get early access

cendarum run

Terminal window
cendarum run [--project <name|id>] --env <name|id> [--only KEY,KEY] \
[--email <email>] [--shell] -- <cmd> [args...]

Everything after -- is executed verbatim. --env is required, and so is the -- separator with at least one argument after it.

Terminal window
cendarum run --env dev -- npm run dev
cendarum run --project acme-api --env staging -- ./bin/server
cendarum run --env dev -- printenv API_URL
Flag Meaning
--env <name|id> Required. Which environment to inject
--project <name|id> Disambiguates when the environment name exists in more than one project
--email <email> Account email. Falls back to CENDARUM_EMAIL, then an interactive prompt
--only KEY,KEY Inject only these keys. Repeatable; the lists are combined and de-duplicated, and surrounding whitespace is trimmed. An empty key is a usage error
--shell Run the command through your shell, for pipes and &&. Off by default
-h, --help Print usage

The child’s environment is your real environment, with Cendarum’s managed keys overlaid on top.

Terminal window
$ API_URL=ambient cendarum run --env dev -- printenv API_URL
https://api.example.test

The vault value wins, because the vault is the source of truth for the keys it manages. Everything else in your environment — PATH, HOME, TERM, your shell’s variables — passes through untouched.

--only narrows what gets injected; anything outside that list is left exactly as it was.

This also settles the dotenv question. Node’s dotenv, python-dotenv, and essentially every other loader default to “an existing environment variable wins”, so a framework that auto-loads .env at startup will find our value already present and leave it alone. run just works.

Some variable names don’t configure your program — they configure the machinery that starts your program. A stored value for one of those could hijack every process launched under cendarum run, which matters because a teammate with a write grant can edit your environment.

So those names are skipped, with a warning naming the key:

cendarum: 'LD_PRELOAD' excluded (loader denylist); pass --only LD_PRELOAD to inject deliberately

The list is:

Names
Prefixes LD_, DYLD_
Exact PATH, NODE_OPTIONS, PYTHONSTARTUP, PYTHONPATH, PYTHONHOME, RUBYOPT, PERL5OPT, PERL5LIB, BASH_ENV, ENV, ZDOTDIR, IFS, SHELLOPTS, GLIBC_TUNABLES

Naming a key in --only overrides the skip — the escape hatch is explicit and per-key, so you can do it when you mean to and can’t do it by accident.

NODE_ENV is not on the list, and neither is anything else that merely configures your application. Only loader and interpreter bootstrap names are.

Off by default, because cendarum run -- npm start executing npm directly is both faster and free of quoting surprises. Turn it on when you need shell syntax:

Terminal window
cendarum run --env dev --shell -- 'echo $FEATURE_FLAG && printenv API_URL'

With --shell, exactly one argument may follow -- — quote the whole command. Two arguments is a usage error rather than a silent surprise.

run is transparent, so it composes inside scripts and Makefiles:

Code Meaning
the child’s Whatever your command exited with, passed straight through
128 + n The child was killed by signal n (Unix shell convention)
127 The command was not found
126 Found, but not executable
2 Usage error — a bad flag, a missing --env, a missing --
Terminal window
$ cendarum run --env dev -- sh -c 'exit 42'; echo "exit=$?"
exit=42

stdin, stdout, and stderr are passed through untouched, so interactive programs, pagers, and progress bars behave normally. SIGINT and SIGTERM are forwarded to the child.

On a terminal, Ctrl-C reaches the child twice — once from the kernel delivering to the whole process group, once from our forward. Harmless for termination, and the alternative (taking over terminal job control) would stop any child that tried to read from the terminal.

  • It does not keep watching. The values are a snapshot taken at spawn. A rotation, a revoked grant, or a locked vault does not reach a running process. Restart it.
  • It does not work headless. A human types the master password. CI needs a machine identity, which is a different branch of the key hierarchy and deliberately out of scope for now — see the FAQ.
  • It does not resolve a trust prompt. If your account keys changed in a way that needs review, run refuses with bundle_review_required and sends you to the desktop app, which is where the trust-review interface lives.

Once injected, the values live in the child’s process environment, and on Linux that means /proc/<pid>/environ and ps e expose them to anything running as your user for as long as the process lives.

That is inherent to environment injection and true of every tool that does it. What it buys you is the trade from “in a file on disk indefinitely, copied to every machine, one git add -A from being published” to “in one process, while it runs”. We think that’s clearly the better side of the trade, and we’d rather state the terms than imply there aren’t any.