Paste this section into the CLAUDE.md of every project (or link it — the directive at https://hi.jbnx.io keeps always-apply files to a pointer). It tells any agent where credentials live, and — more importantly — that there is no key file to go looking for.
There are no secrets in this repository and none on the developer's disk. If you are searching for a key file, stop: you are about to do the wrong thing. Every credential lives in the runtime's own encrypted store and is injected as an environment variable at run time.
| Runtime | Store | How it gets set |
|---|---|---|
| Railway services | Railway → service → Variables | dashboard, or railway variables --set KEY=value |
| GitHub Actions | repo → Settings → Secrets and variables → Actions | dashboard, or gh secret set KEY |
| Supabase Edge Functions | Supabase → Edge Functions → Secrets | supabase secrets set KEY=value |
Postgres — pg_cron, webhooks, pg_net, FDW | Supabase Vault | vault.create_secret('value','name','desc') |
| Local development | a gitignored .env | copy .env.example and fill it in yourself |
.env.example is the registry of every variable this project needs. It lists names and never values. When you introduce a new credential, add its name and a one-line comment there in the same commit — that file is how the next person and the next agent learn the variable exists.
process.env.NAME, Deno.env.get('NAME'), os.environ['NAME']. Never inline a literal key, not even one that looks like a placeholder..env, .env., .pem, .key, id_rsa, credentials.json, .npmrc, .netrc, anything under .aws/, .ssh/, or a path containing secret. You need the variable's name* to write correct code. You never need its value. const key = process.env.SUPABASE_SERVICE_KEY;
if (!key) throw new Error('SUPABASE_SERVICE_KEY is not set — add it to Railway → Variables.');
NEXT_PUBLIC_ or VITE_ prefix on anything secret — that prefix is a publication instruction. No logging a request that carries an Authorization header. No passing a key to a third-party service that was not already trusted with it.Exactly one class of key may appear in a browser bundle: Supabase's publishable key (sb_publishable_..., formerly anon). It is RLS-enforced and safe to expose.
Everything else is backend-only: the Supabase secret key (sb_secret_..., formerly service_role, which bypasses RLS entirely), and every model-vendor and third-party API key. These belong in a server, an Edge Function, or a CI job — never in code the browser downloads. Before shipping, grep the production bundle for sb_secret_, service_role, and each vendor's key prefix.
Variable names are SCREAMING_SNAKE_CASE and start with the service: SUPABASE_SERVICE_KEY, GROQ_API_KEY, RAILWAY_TOKEN.
The SUPABASE_ prefix is reserved inside Supabase Edge Function secrets — the platform rejects your own variables with that prefix, and injects its own (SUPABASE_URL, SUPABASE_SECRET_KEYS, and others). Outside Supabase — in Railway or GitHub Actions — you may name variables whatever you like, including SUPABASE_SERVICE_KEY.
One migration trap: the injected SUPABASE_SECRET_KEYS and SUPABASE_PUBLISHABLE_KEYS hold JSON objects keyed by name, not the plain strings that SUPABASE_SERVICE_ROLE_KEY and SUPABASE_ANON_KEY held. Code written against the old vars breaks silently:
const keys = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!);
const admin = createClient(Deno.env.get('SUPABASE_URL')!, keys['default']);
Legacy anon / service_role JWTs still work but are deprecated by the end of 2026. Prefer the new format for anything you write now.
Directive context: https://hi.jbnx.io §5 (live host decides) and /ref/R4 (how to ask). Source: CEO, 2026-09-18.