12 · Best practice
Agent behavior: secrets and state
Agent prompts never hold raw credentials — env-var references only. The agent confirms its environment and available secrets before any write action.
Practice 07 keeps secrets out of the repo. This practice keeps them out of the prompt.
An agent that reads $API_KEY from the environment is safe to commit, clone, and run anywhere. An agent with a raw credential in the prompt is a liability from the moment you paste it. The same logic applies to environment state: an agent that infers it's "probably in dev" will eventually be wrong at the worst possible time.
The problem
Agent prompts can hold secrets in two ways — explicit (you paste a token in) and implicit (you include a URL or config value that encodes the environment). Both are wrong.
State is the other half of the problem. An agent that writes to a database without confirming $ENVIRONMENT first will write to whichever database the current shell points at. In a dev-heavy team, that's usually fine. In a team where prod credentials are in the same .env as everything else, it isn't.
Requirements
- Reference secrets via env vars only (
$API_KEY, never the key itself) - Agent confirms the active environment at session start before any write action
- No hardcoded URLs, tokens, or passwords in the prompt or generated code
- If a secret is missing, halt with a clear error — never substitute a default
How to do it
Write every secret reference as an env var. The prompt names the variable; the shell holds the value.
Good: "Use $DATABASE_URL to connect to the database."
Bad: "Use postgres://user:hunter2@db.prod.example.com/app."
Start every agent session with an environment confirmation block. Paste this template into your agent's system prompt and fill in the variable names your project uses.
You are a coding agent working on [PROJECT NAME].
## Environment
Active environment: ${ENVIRONMENT}
You have access to the following secrets via environment variables:
- $DATABASE_URL
- $API_KEY
- $WEBHOOK_SECRET
Before taking any write action (insert, update, delete, deploy),
confirm the active environment and list the secrets you can access.
If any required secret is missing, halt and output:
"MISSING SECRET: [variable name]. Cannot proceed."
Never hardcode credentials. Never substitute default values for missing secrets.
Never infer the environment — always read from $ENVIRONMENT explicitly.
## Task
[TASK DESCRIPTION GOES HERE]
Add ENVIRONMENT to your .env.example alongside the other keys. The value in .env tells the agent which world it's in.
ENVIRONMENT=dev
API_KEY=test-key-do-not-use
Use a distinct ENVIRONMENT value per tier (dev, staging, prod). Never share the same .env file across tiers.
Verify the setup works: run the agent in dev and confirm its first output names the environment and lists the secrets it found. If a required var is missing, the agent should halt — not proceed with a fallback value.
Acceptance checks:
- Run the agent in dev without it touching prod data
- A teammate can clone the repo and run it without being sent credentials
- The agent's first output confirms: environment name + which secrets it has access to
When a secret is missing
The agent halts. That's the feature, not a bug. A silent fallback — empty string, localhost, a test key you forgot to rotate — is how agents cause incidents in production. Loud failure at startup beats quiet failure mid-task.
If your agent substitutes a default value when a secret is missing, it will eventually run in an environment where that default points somewhere real.
Anti-patterns
- Pasting a token into the system prompt "just for testing." It's in your conversation history now.
- Letting the agent infer the environment from a URL or hostname. Read
$ENVIRONMENTexplicitly. - A missing secret that silently becomes an empty string or
localhost. Halt instead. - Hardcoded database URLs that look like dev but point to prod under certain shell configs.
- Sharing one
.envfile across dev and staging because "they're basically the same."
Contributed by Chris Lee.