Summary
Today's op-env onboarding for a new secret required walking through ~8 coordinated manual steps across multiple files, with several non-obvious friction points along the way. Proposing a guided op-env add <ENV_VAR> subcommand (and a few sibling commands) that turns the multi-file, multi-tool dance into a single interactive flow.
This issue is intentionally scoped to be a cold-start-readable design discussion, not a finished spec. The goal is to capture the real friction I just hit so a future session can pick this up and refine into an implementation plan.
Background — what triggered this
While working in a Claude Code session, I needed to add a Bright Data API token to op-env so the BrightData MCP server could read it at runtime instead of having the token hardcoded in ~/.claude.json. The wiring path turned out to be longer than I'd remembered. I went through it manually on purpose so I could feel the friction.
What I noticed:
- Each step is individually small, but they span three different files and four different command-line tools (
op, op-env, code/claude, plus whatever the downstream consumer is)
- The order matters — if you update
~/.claude.json before verifying the template resolves, you can end up with a broken state that's harder to diagnose
- The vault UUID / item UUID / field lookup is the most error-prone part. I made a shell-syntax mistake (literal angle brackets in
op item get) that produced a confusing "no such file or directory" error
- There is no obvious place in the README or the
--help output that walks through "add a new key end-to-end." The README explains the what and the architecture, but the operational add-a-key workflow is implicit
- The
--check flag is excellent for validation but only kicks in after you've already authored the template line correctly
Manual flow today (what I just did)
For a single new secret going through op-env into a downstream consumer (Claude Code MCP server in this case):
- Find the vault UUID —
op vault list --format=json | jq (or remember it from earlier work)
- Find the item UUID —
op item list --vault <vault-uuid> | grep -i <item-name>
- Find the field name —
op item get <item-uuid> --format json | grep -E '"id"|"label"' (and intuit which field actually holds the credential value — typically credential, but not always)
- Construct the reference — manually assemble
op://<vault-uuid>/<item-uuid>/<field>
- Append the line to the template — open
~/.claude/.env.tpl in an editor, add MY_VAR=op://... near alphabetical neighbors
- Verify resolution —
op-env --check and confirm the new key shows ✓
- Update the downstream consumer — for an MCP server, open
~/.claude.json, find the relevant env block, change the hardcoded value to "${MY_VAR}"
- Restart in a way that re-reads everything — full quit of Claude Code (or the parent app), relaunch via
op-env claude (or op-env code for the VS Code extension)
- Verify at runtime —
printenv MY_VAR in the new session to confirm injection landed
Steps 7-9 are out of op-env's scope strictly speaking, but they're part of the workflow that op-env users actually need to complete. A useful tool would at minimum acknowledge that these steps exist (a "Next steps:" printout after the template line is added).
Pain points worth optimizing
| # |
Friction |
Why it matters |
| 1 |
op vault list + op item list + op item get is three commands to find one reference |
Most users only need vault → item → credential, but they have to know the API |
| 2 |
The reference syntax op://vault/item/field is easy to typo |
A single character off and op read returns nothing useful |
| 3 |
No interactive picker — copy/paste UUIDs between commands is brittle |
This is where my "no such file or directory" zsh redirection error happened |
| 4 |
The template file has no schema validation beyond op read succeeding |
A typo in the env-var NAME silently breaks downstream interpolation but resolves fine in --check |
| 5 |
The "what file consumes this var?" knowledge is in the user's head |
Especially painful for MCP servers in ~/.claude.json where the binding isn't obvious |
| 6 |
No "test the consumer received it" step |
op-env --check ✓ does not imply the child process actually sees the var (e.g., GUI-launched VS Code) |
Proposed feature — op-env add <ENV_VAR_NAME>
A guided onboarding subcommand that walks the user from "I have a secret in 1Password" to "the template line is written and verified." Sketch:
$ op-env add BRIGHTDATA_API_TOKEN
op-env: adding BRIGHTDATA_API_TOKEN to ~/.claude/.env.tpl
[1/4] Select vault:
1) Credentials-Workflow-Tools (VAULT_UUID)
2) Private (VAULT_UUID_B)
3) Personal (VAULT_UUID_C)
> 1
[2/4] Search items in Credentials-Workflow-Tools (or press enter to list all):
> bright
1) BrightData API Key
2) BrightData MCP Token (backup)
> 1
[3/4] Select field:
1) credential (default)
2) notesPlain
3) username
> 1 (or press enter for default)
[4/4] Resolving op://VAULT_UUID/ITEM_UUID/credential ...
✓ Resolved (first 2 chars: **)
Appending to ~/.claude/.env.tpl:
BRIGHTDATA_API_TOKEN=op://VAULT_UUID/ITEM_UUID/credential
Done. Next steps:
• Update the consuming config file to reference ${BRIGHTDATA_API_TOKEN}
(common locations: ~/.claude.json mcpServers blocks, .env consumers, app configs)
• Quit and relaunch the consuming process so it inherits the new env var
• Run `printenv BRIGHTDATA_API_TOKEN` in the new shell to confirm
Key behaviors:
- Non-interactive mode supported:
op-env add MY_VAR --vault <uuid> --item <uuid> --field credential for scripting / CI / re-running from history
--dry-run flag that prints the line that would be appended without writing
- Reuses the existing
--tpl flag for non-default template paths
- Fails closed: aborts before writing if resolution fails
Adjacent companion commands (open for debate)
| Command |
Purpose |
Priority |
op-env add |
The big one above |
P0 |
op-env list |
Print all template keys with resolution status and (optional) which file consumes each |
P1 |
op-env rotate <VAR> |
Walk the user through rotating a leaked credential — 1Password update + cache invalidation reminder |
P1 |
op-env scaffold |
Detect unwired secrets in known consumers (e.g., ~/.claude.json MCP env blocks with hardcoded values) and propose migration |
P2 |
op-env init |
Bootstrap a fresh ~/.claude/.env.tpl from a chosen vault |
P2 |
op-env add --for claude-code-mcp <server> |
One-shot: add the var AND update the ~/.claude.json MCP block to interpolate it |
P2 — high value, high risk (touches a file op-env doesn't own today) |
The P2 "consumer-aware" mode is the most powerful but also the biggest scope expansion — op-env today is deliberately narrow (read template, exec command). Touching downstream config files is a meaningful boundary shift. Worth a separate issue once add lands.
Code-area touch points
| Area |
What changes |
bin/op-env |
Subcommand routing — detect first non-flag arg as subcommand verb (add, list, rotate...). Today's op-env <command> [args] becomes op-env [subcommand] [flags] [command] [args]. Backward compatibility: any first arg that isn't a known subcommand keeps existing exec-target behavior. |
bin/op-env (new functions) |
op_env_add() orchestrating the 4-phase flow. Uses op vault list --format=json + op item list --format=json + op item get --format=json + jq for selection. read -r -p for prompts. |
templates/env.tpl.example |
Possibly add a header comment noting op-env add as the recommended way to add lines |
docs/architecture.md |
Add an "Adding a new secret" section that walks through both manual and op-env add paths |
README.md |
Promote op-env add to the Quick start example |
test/test-op-env.sh |
New test cases mocking op vault list / op item list / op item get and feeding scripted stdin |
.github/workflows/... |
Existing checks (B1 no exfiltration, B2 TTY preservation, F1/F2 complexity flags) should already cover the new code — verify the new path doesn't introduce a non-exec exit |
Dependencies to consider:
jq becomes a hard dependency for add (currently soft). Already in the README as a usage hint, so probably fine
- Optional:
fzf for nicer selection if present, fall back to numbered list if not
- Optional:
gum (Charm) for prettier prompts — same fallback story
Explicitly out of scope for v1
- Writing to
~/.claude.json or any other consumer config (defer to the --for P2 idea)
- A TUI / full-screen UI — keep it line-oriented to match the rest of op-env's style
- Vault discovery across multiple 1Password accounts (use the active default account)
- Editing existing template lines (use the manual flow, or implement later as
op-env edit)
Acceptance sketch (for the future session that picks this up)
A working op-env add is shipped when:
op-env add MY_VAR walks an interactive flow that ends with a new line in the template
- The new line resolves via
op-env --check immediately after
- Existing 56 tests still pass
- New test coverage exists for: vault selection, item selection, field selection, resolution failure abort,
--dry-run, and non-interactive flags
- The README's "Adding a new secret" section is updated to point at the new command first, with manual flow as fallback
- A "Next steps" guidance block prints after the line is added, calling out the most common downstream consumers (Claude Code MCP servers being the prime example)
Open questions
- Should
op-env add be smart enough to detect if ~/.claude.json has hardcoded values matching the just-added secret and offer to migrate them? Yes-but-as-a-follow-up feels right.
- Is interactive-only acceptable for v1, or do the non-interactive flags need to land at the same time? My intuition says interactive-only is fine for v1 — scripted usage is a power-user feature
- How verbose should the "Next steps" block be? Drift between "minimal" (just the variable name) and "opinionated" (specific paths for Claude Code) is a real tradeoff
This issue is intentionally written to be readable cold. If you're picking this up in a future session, the manual flow section is the most important context — that's the lived experience this feature is trying to remove.
Summary
Today's op-env onboarding for a new secret required walking through ~8 coordinated manual steps across multiple files, with several non-obvious friction points along the way. Proposing a guided
op-env add <ENV_VAR>subcommand (and a few sibling commands) that turns the multi-file, multi-tool dance into a single interactive flow.This issue is intentionally scoped to be a cold-start-readable design discussion, not a finished spec. The goal is to capture the real friction I just hit so a future session can pick this up and refine into an implementation plan.
Background — what triggered this
While working in a Claude Code session, I needed to add a Bright Data API token to op-env so the BrightData MCP server could read it at runtime instead of having the token hardcoded in
~/.claude.json. The wiring path turned out to be longer than I'd remembered. I went through it manually on purpose so I could feel the friction.What I noticed:
op,op-env,code/claude, plus whatever the downstream consumer is)~/.claude.jsonbefore verifying the template resolves, you can end up with a broken state that's harder to diagnoseop item get) that produced a confusing "no such file or directory" error--helpoutput that walks through "add a new key end-to-end." The README explains the what and the architecture, but the operational add-a-key workflow is implicit--checkflag is excellent for validation but only kicks in after you've already authored the template line correctlyManual flow today (what I just did)
For a single new secret going through op-env into a downstream consumer (Claude Code MCP server in this case):
op vault list --format=json | jq(or remember it from earlier work)op item list --vault <vault-uuid> | grep -i <item-name>op item get <item-uuid> --format json | grep -E '"id"|"label"'(and intuit which field actually holds the credential value — typicallycredential, but not always)op://<vault-uuid>/<item-uuid>/<field>~/.claude/.env.tplin an editor, addMY_VAR=op://...near alphabetical neighborsop-env --checkand confirm the new key shows ✓~/.claude.json, find the relevantenvblock, change the hardcoded value to"${MY_VAR}"op-env claude(orop-env codefor the VS Code extension)printenv MY_VARin the new session to confirm injection landedSteps 7-9 are out of op-env's scope strictly speaking, but they're part of the workflow that op-env users actually need to complete. A useful tool would at minimum acknowledge that these steps exist (a "Next steps:" printout after the template line is added).
Pain points worth optimizing
op vault list+op item list+op item getis three commands to find one referenceop://vault/item/fieldis easy to typoop readreturns nothing usefulop readsucceeding--check~/.claude.jsonwhere the binding isn't obviousop-env --check ✓does not imply the child process actually sees the var (e.g., GUI-launched VS Code)Proposed feature —
op-env add <ENV_VAR_NAME>A guided onboarding subcommand that walks the user from "I have a secret in 1Password" to "the template line is written and verified." Sketch:
Key behaviors:
op-env add MY_VAR --vault <uuid> --item <uuid> --field credentialfor scripting / CI / re-running from history--dry-runflag that prints the line that would be appended without writing--tplflag for non-default template pathsAdjacent companion commands (open for debate)
op-env addop-env listop-env rotate <VAR>op-env scaffold~/.claude.jsonMCP env blocks with hardcoded values) and propose migrationop-env init~/.claude/.env.tplfrom a chosen vaultop-env add --for claude-code-mcp <server>~/.claude.jsonMCP block to interpolate itThe P2 "consumer-aware" mode is the most powerful but also the biggest scope expansion — op-env today is deliberately narrow (read template, exec command). Touching downstream config files is a meaningful boundary shift. Worth a separate issue once
addlands.Code-area touch points
bin/op-envadd,list,rotate...). Today'sop-env <command> [args]becomesop-env [subcommand] [flags] [command] [args]. Backward compatibility: any first arg that isn't a known subcommand keeps existing exec-target behavior.bin/op-env(new functions)op_env_add()orchestrating the 4-phase flow. Usesop vault list --format=json+op item list --format=json+op item get --format=json+jqfor selection.read -r -pfor prompts.templates/env.tpl.exampleop-env addas the recommended way to add linesdocs/architecture.mdop-env addpathsREADME.mdop-env addto the Quick start exampletest/test-op-env.shop vault list/op item list/op item getand feeding scripted stdin.github/workflows/...Dependencies to consider:
jqbecomes a hard dependency foradd(currently soft). Already in the README as a usage hint, so probably finefzffor nicer selection if present, fall back to numbered list if notgum(Charm) for prettier prompts — same fallback storyExplicitly out of scope for v1
~/.claude.jsonor any other consumer config (defer to the--forP2 idea)op-env edit)Acceptance sketch (for the future session that picks this up)
A working
op-env addis shipped when:op-env add MY_VARwalks an interactive flow that ends with a new line in the templateop-env --checkimmediately after--dry-run, and non-interactive flagsOpen questions
op-env addbe smart enough to detect if~/.claude.jsonhas hardcoded values matching the just-added secret and offer to migrate them? Yes-but-as-a-follow-up feels right.This issue is intentionally written to be readable cold. If you're picking this up in a future session, the manual flow section is the most important context — that's the lived experience this feature is trying to remove.