Community threads

Can llm.apiKey at instance level use secret_ref, or is that pattern only for agent env vars?

Justin Simpson · 2026-05-02

I'm running Paperclip (2026.428.0) via the npm-installed CLI (paperclipai) on macOS, with a single instance hosting one company (nine agents, all on claudelocal). Local encrypted secrets provider is configured and strict mode is enabled.

Background. While auditing my installation I noticed my Anthropic API key was sitting in plaintext at llm.apiKey in ~/.paperclip/instances/default/config.json. I deactivated the old key, generated a new one, and reinstalled it via paperclipai configure --section llm. The configure flow accepted the new key as inline plaintext despite strict mode being on, and the new key now lives at llm.apiKey as a string.

The question. I would like the API key to be stored in the local encrypted secret store rather than inline. The /docs/api/secretspage documents secretref as the binding format inside agent adapter env blocks, but it does not address the instance-level llm.apiKey field. I have not been able to find a CLI path either: there is no secrets subcommand at the top level, and paperclipai company only exposes list, get, feedback, export, import, and delete.

Specifically:

Does llm.apiKey at instance level accept the secretref object format ({type, secretId, version}), or is the instance-level field intentionally inline-only? If inline-only, what is the recommended pattern for users who want strict mode to bite at this layer? Should secretrefbe applied only at the agent adapterConfig.env level, with the instance llm.apiKey understood as a fallback default? Is there a pnpm secrets:migrate-inline-env --apply equivalent available from the npm-installed CLI, or is that script source-repo-only? If the right answer is "use the API directly", is there a worked example showing POST /api/companies/{id}/secretsfollowed by the corresponding agent config update for a default claudelocal key?

Happy to test and report back. Strict mode currently lets inline through at config-write time, which I can also share more detail on if useful.

Thanks.

Answers

Aron Prins · 2026-05-03

Great write-up. I went and traced this through the source rather than guess from the docs — answers are more definitive than I'd otherwise be comfortable giving, and your "strict mode lets inline through at config-write time" observation is a real bug, not a misread on your end.

Going through your four questions:

1. Does llm.apiKey at instance level accept secretref?

No — and not just by convention. The Zod schema in packages/shared/src/config-schema.ts is:

ts export const llmConfigSchema = z.object({ provider: z.enum(["claude", "openai"]), apiKey: z.string().optional(), });

It's a plain optional string. Any object form (including { type: "secretref", ... }) would fail validation on read. So even hand-editing config.json wouldn't work — you'd just get a config-load error on next start.

The structural reason: secretref is a per-adapter env concept throughout the codebase. Every consumer iterates over adapterConfig.env — the per-adapter build-config.ts files (claude-local, codex-local, gemini-local, cursor-local, pi-local, opencode-local, acpx-local), the EnvVarEditor.tsx component, the redaction layer, the company portability service, and the migration script. Nothing reads secretref from config.llm.

2. Recommended pattern

Bind everything per-agent in adapterConfig.env, treat the instance-level llm.apiKey as a fallback default that nothing actually resolves through, and consider leaving it empty/null once your nine claudelocal agents each have their own:

json { "adapterConfig": { "env": { "ANTHROPICAPIKEY": { "type": "secretref", "secretId": "<your-secret-uuid>", "version": "latest" } } } }

Justin Simpson · 2026-05-03

Many thanks for the careful walk-through. The schema citation made the question close cleanly, and the worked API example was exactly the right level of detail.

Migration ran successfully on this end. One secret on the company, nine claudelocal agents bound to it via adapterConfig.env.ANTHROPICAPIKEY with version: "latest". Future key rotation is now a single POST /api/secrets/<id>/rotate, which is the right shape.

Small build-shape note for anyone else following along. On this version of Paperclip the agent PATCH endpoint is /api/agents/<agentId> (flat), not /api/companies/<id>/agents/<agentId> as in the example. List endpoints are still company-nested, only mutate is flat. The 404 is a fast tell once you've hit it.

The two upstream items flagged for @dotta both feel worth landing. Happy to track whatever issue or PR comes out of them.