Product·Env & secrets

Secrets stay
on the host.

Set per-repo environment variables once for Code sessions. Your existing .env files just work, paste KEY=value anywhere and it is captured instead of sent to the model, values stay Keychain-masked, and everything injects into agent spawns and terminals. Continuum briefs the agent with CONTINUUM_MANAGED_ENV_KEYS and refuses keys that hijack the runtime.

CodeEnvSettings
clawdmeter
repo · ~/src/clawdmeter3 keys
DATABASE_URL••••••••••••
STRIPE_SECRET••••••••••••
OPENAI_API_KEY••••••••••••
host keychainnot in git · not in cloud
01 · Scope

Per-repo. Nothing global.

Env bindings attach to a canonical repo root, not to a random shell profile. Every Code session and terminal spawned in that project gets the same injection set.

Canonical means canonical. A trailing slash, a symlinked checkout, and the /private spelling of the same path all resolve to one scope. Getting that wrong is not a cosmetic bug: it silently resolves to no variables at all, and you spend an afternoon wondering why the agent claims your database is unreachable.

Scopes follow canonical repository roots, so switching repos switches secrets with no global bleed into unrelated worktrees. Fits the same workbench as Code.

repo-scoped stable root no shell bleed
Env
scope
~/src/clawdmeter3 keys
~/src/defx-frontend1 key
~/src/continuum-web0 keys
canonical git rootper-repo only
02 · Your own files

The .env you already have needs no import step.

Add a repo and its existing .env, .env.local and .env.* variables are read at launch and injected into every session, terminal and agent in that project. There is no migration and nothing to click first.

01 · referenced

Never copied

The files are read, not imported. Edit .env and the next launch picks it up. Continuum never writes your file's values back into a managed block where a stale copy would shadow the real one.

02 · precedence

Yours wins over the file

A variable you set in Continuum takes precedence over a repo file of the same name. Placeholder files like .env.example are skipped entirely, so a template never quietly becomes your config.

03 · untrusted

A clone can't reach in

Loader and hook keys found in a repo file (DYLD_*, NODE_OPTIONS, GIT_SSH_COMMAND) are refused. Cloning someone's repository cannot hand its .env a path into your agent's process.

Settings also lists the keys it found in those files, masked, with an Adopt action if you want Continuum to own one. Nothing is written until you adopt, and the files themselves are never modified. That last sentence is the point: this is a repo you keep working in with your own tools, not a repo Continuum has taken over. The same instinct shows up in how a containerised agent gets its config, where the boundary between the image and your secrets is the whole design.

Env
import
adopt .env .env · .env.local · detected
paste KEY=value
DATABASE_URL=postgres://…
STRIPE_SECRET=sk_live_… → redacted
capture · mask · storevalues never re-shown
03 · Capture & import

Paste a key into a chat and it never reaches the model.

Type ACME_API_KEY=… into a session and Continuum saves it to that repo's variables, redacts it from the prompt, and confirms with an undo toast. It works in Code, for any key name, not a short list of known vendors.

Capture is deliberately conservative about what counts. Assignments inside fenced code blocks are left alone, lower-case names are ignored, and placeholders like <your-key> are not stored. Anything that controls process loading or endpoint routing is refused outright rather than quietly saved.

Bulk import gets a preview first. Every line is classified as ready, duplicate, invalid, empty, or skipped, with the line number, before anything is written. You choose how duplicates resolve: skip, overwrite, or create disabled drafts you can review and enable one at a time.

After save, the UI shows a mask plus a short non-reversible fingerprint. The fingerprint is enough to tell one environment's value from another's at a glance without ever showing the secret, and reveal stays local to the Mac. Less shoulder-surfing, less accidental screenshot leak.

04 · Inject

Agents and terminals get the same map.

Managed keys inject into agent spawns and embedded terminals for that repo, so echo $MY_KEY works in the same pane the agent is reading from. It is agent-agnostic: the same set reaches a Claude PTY, a harness provider, and an OpenCode run alike. The agent also receives CONTINUUM_MANAGED_ENV_KEYS, a names-only list, so it can see what exists without dumping its whole environment into the transcript.

The briefing is more than a variable. Claude sessions get a one-line system prompt naming those keys and telling the agent to read them from the environment rather than asking you to paste them. Codex, Cursor and Grok get the same briefing carried in front of the first turn of each provider thread. It rides on the text sent to the provider and never on the transcript echo, so nothing extra appears in your conversation.

Because dotenv-aware tools load files rather than inherited environments, the resolved set is also written into .env.local between explicit begin and end markers, with a comment saying it is managed. Vite, your test runner, and the agent's own child processes then see the same values the agent does. The block is rewritten every launch, and a variable you hand-wrote in that file with the same name is reported as a conflict rather than silently overwritten. Scoping which commands run at all is a separate lever, covered by Claude Code permissions and the corresponding Codex sandbox modes.

spawn env terminals managed keys list
session env · clawdmeter
# injected by Continuum
DATABASE_URL=••••
STRIPE_SECRET=••••
CONTINUUM_MANAGED_ENV_KEYS=DATABASE_URL,STRIPE_SECRET,OPENAI_API_KEY
# agent sees names · not values
Env
guard
PATHrefused
LD_PRELOADrefused
DYLD_INSERT_LIBRARIESrefused
APP_SECRETaccepted
dangerous overrides can’t hijack the runtime
05 · Refuse dangerous keys

Not every variable is allowed.

A variable is not just data when the process reading it will happily execute what it names. Continuum refuses the whole exec surface: PATH, dynamic-linker controls (DYLD_*, LD_*, anything containing PRELOAD), shell startup and field-splitting (BASH_ENV, PROMPT_COMMAND, IFS), interpreter hooks (NODE_OPTIONS, PYTHONPATH, PERL5OPT, RUBYOPT), and git's shell-outs (GIT_SSH_COMMAND, GIT_EXTERNAL_DIFF, the GIT_CONFIG family).

The family people forget is the program-exec hooks. EDITOR, PAGER, LESSOPEN, BROWSER and anything ending in ASKPASS name a program that git, ssh, sudo or less will run for you. Point one at a script and you get code execution the next time the agent runs git commit. None of them is a repo secret, so none of them is allowed.

The gate runs twice, at different strictness. The spawn boundary strips that set from every resolved environment, before the managed block is written, so a hand-edited store cannot revive an injection on the next dotenv read. The import gate is stricter still: it also blocks TLS downgrade, proxy interception, CA-bundle overrides, and endpoint redirects such as *_BASE_URL or a package index, because re-pointing the agent's own endpoint steals your subscription token. The rules are anchored so ordinary connection strings still import: DATABASE_URL and SUPABASE_URL are fine. App secrets and API keys are welcome. Shell takeover is not. Full posture lives next to security, and the threat model it defends is laid out in AI coding agent security.

06 · Trail

Every change is written down.

Created, updated, value updated, rotated, deleted, assignment changed, imported. Each event records what happened and when, so "who changed the staging key last Thursday" is a question with an answer rather than a Slack thread. A rotation is recorded distinctly from an ordinary edit, and the value fingerprint is recomputed on every write, so two scopes can never keep claiming they share a value after one of them was rotated.

Variables carry a kind (sensitive, plain or system) and a scope, so a connection string and a feature flag are not treated as the same kind of thing on screen. Sets let one repo hold more than one environment with one active at a time, which is how a staging profile and a local profile coexist without a comment-out ritual in a file. None of this is a database in the cloud: the records live on the host, and the values live in its Keychain.

iPhone, web, and the Windows and Linux desktop apps all read and edit the same store through the host, so the surface you use is a matter of where you are sitting. This is per-repo application configuration and it stays out of the way of provider settings, which live in Claude Code's own settings files and Codex's config.toml respectively.

07 · Questions

The honest fine print.

Short answers here, long answers in the docs.

Values don't. Secrets stay on the host that runs the agent. The phone can manage which variables exist for a repo, but it doesn't download your DATABASE_URL to steer a session.

No. Managed env is host storage plus Keychain, not a git write. Your own .env files are read and never modified. Keep ignoring .env in the repo as you already do.

A list of key names Continuum injected for this spawn, so the agent knows which secrets exist without printing their values into the thread. The instruction is scoped to those names, so the agent can still ask you for a genuinely new variable.

Provider authentication still goes through Continuum's provider accounts and Keychain partitions. Managed env is for app and runtime secrets in the repo, not a back door that bypasses multi-account isolation.

Because something in the toolchain would execute it rather than read it: a loader path, a shell startup hook, a program-exec hook like PAGER or an ASKPASS variable, or an endpoint redirect on the import path. Rename the concept if you can; if you genuinely need PATH changed, change it in your shell, not in a repo's variables.

You get a conflict, not a silent overwrite. Continuum only rewrites its own marked block and stops rather than clobbering a line you wrote by hand. Remove the manual line or drop the managed variable and the launch proceeds.

08 · Begin

Paste once.
Never into the thread again.

Add a repo and the .env you already trust is live on the next launch.

see also · code · security