Support 1Password op:// secret references in settings.json env section

Status Open
Maintainer reply None cached
Activity 12 comments · opened Feb 6, 2026

Feature Request

Support op:// secret references in the env section of settings.json (and .claude/settings.json), so that API keys and other secrets can be resolved from 1Password at startup without wrapper scripts.

Desired behavior

{
  "env": {
    "ANTHROPIC_API_KEY": "op://Private/Claude API Key/credential"
  }
}

Claude Code would detect op:// prefixed values and resolve them via op read (1Password CLI) before use.

Current workarounds

  1. apiKeyHelper script that calls op read — works for ANTHROPIC_API_KEY only, not for arbitrary env vars (e.g., MCP server tokens).
  2. op run -- claude wrapper — works but requires a separate .env file and remembering to always launch Claude Code through the wrapper.

Why this would be useful

  • 1Password CLI (op) is widely adopted for secret management in developer workflows
  • Many tools already support op:// references natively (e.g., Docker, various CI systems)
  • It would allow secure, declarative secret injection for both API keys and MCP server environment variables without wrapper scripts
  • The env section in settings.json is the natural place to configure this

Implementation notes

  • Only resolve op:// references if op CLI is available in $PATH
  • Could generalize to a pluggable secret resolution mechanism (e.g., aws-secretsmanager://, vault://) in the future
  • Should fail with a clear error message if op is not installed or not authenticated

---
This issue was generated with Claude Code

View original on GitHub ↗

12 Comments

ei-grad · 6 months ago

Opus 4.6 initially hallucinated this feature, so I asked it to file a feature request :-). Though, ambiguity for url handling there could cause a problematic behavior.

ei-grad · 6 months ago

Good point about the ambiguity. Instead of overloading env values with URL-scheme detection, a dedicated secrets section would be cleaner:

{
  "secrets": {
    "ANTHROPIC_API_KEY": "op://Private/Claude API Key/credential",
    "MCP_GITHUB_TOKEN": "aws-sm://prod/github-token",
    "DB_PASSWORD": "cmd://pass show db/password"
  },
  "env": {
    "SOME_NORMAL_VAR": "plain-value-no-magic"
  }
}

This way:

  • env stays plain — values are used as-is, no surprise resolution
  • secrets explicitly signals that values are URI references to be resolved at startup
  • The URI scheme (op://, aws-sm://, cmd://, etc.) determines the resolver — extensible without ambiguity
  • A generic cmd:// scheme could cover any secret manager via shell command, similar to how apiKeyHelper works but for arbitrary env vars

This avoids the footgun where someone has a legitimate env value starting with op:// and gets unexpected behavior.

---
This comment was generated with Claude Code

github-actions[bot] · 6 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/17719
  2. https://github.com/anthropics/claude-code/issues/15961
  3. https://github.com/anthropics/claude-code/issues/4276

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

ei-grad · 6 months ago

There is ambituity and potential security issue - should this secrets be available only in settings.json context, or like env - passed to environment of everything under claude.

jonathanspiva · 6 months ago

Running into this with a custom Swift MCP server that needs an API token from 1Password. My current workaround in .mcp.json shells out to op read:

{
  "mcpServers": {
    "myserver": {
      "command": "/bin/sh",
      "args": ["-c", "API_TOKEN=$(op read 'op://Vault/Item/credential') exec /path/to/mcp-server"]
    }
  }
}

This works only if op has an active biometric session before Claude Code starts. If the session expires or wasn't established, the MCP server silently fails to connect. The /mcp reconnect command also fails because it spawns the subprocess non-interactively, so op can't prompt for biometric auth. The only fix is to op read manually in a terminal, then restart Claude Code.

Security note: Native support would improve the security posture over current workarounds. To make this reliable today, you end up either pre-exporting the token as an environment variable (visible via ps aux) or maintaining a long-lived op session. Native op:// resolution at startup with a biometric prompt would be both more secure and more reliable.

Worth noting that op:// is a well-established URI scheme already supported by Docker, GitHub Actions, and various CI systems. It's not a novel pattern.

On the auto-detected duplicates:

  • #17719 (shell substitution in MCP env) is a genuine duplicate, same core problem.
  • #15961 (OS Keychain support) is not a duplicate. It proposes native OS keychain URIs (keychain://, credential://, pass://), which is a different approach from op:// resolution. Related but distinct feature.
  • #4276 (env var expansion in settings.json) is not a duplicate. It's about $VAR expansion in settings.json for hooks and general config portability, not about resolving secrets from external credential managers.
ei-grad · 6 months ago

Worth noting that mentioned workaround doesn't help for http MCP servers.

divoxx · 6 months ago

I would also like to express interest in this feature. In addition, based on @ei-grad question, I would like to request those values actually being added to the environment.

Here is an example of how that can be useful:

When interacting with gh, I actually use op plugin run -- gh, which reads my token from 1password and sets GITHUB_TOKEN for the execution of gh. However, because claude code executes each command in a separate bash instance, that causes 1password to request my password for every single gh execution.

Being able to set GITHUB_TOKEN for claude, as part of settings.json, so that it has it readily available for that session, would be great.

baelter · 5 months ago

Research: Why op fails inside Claude Code's sandbox

Spent some time debugging this. Sharing findings for anyone hitting the same wall.

Root cause

The op binary is setgid (-rwxr-sr-x, group onepassword-cli). The 1Password desktop app validates that connecting processes belong to this group before accepting socket connections.

Claude Code's sandbox uses bubblewrap (bwrap), which creates a new user namespace. Inside user namespaces:

  1. Setgid bits are silently ignored (kernel security restriction)
  2. Supplementary groups are stripped down to just the primary group + nobody
# Outside sandbox
uid=1000(user) gid=1000(user) groups=1000(user),150(wireshark),962(lavinmq),968(docker),985(uucp),998(wheel)

# Inside sandbox
uid=1000(user) gid=1000(user) groups=1000(user),65534(nobody)

So op connects to the 1Password daemon socket without the onepassword-cli group credential, and the daemon rejects it with "connection reset".

What doesn't help

  • additionalDirectories with socket paths (~/.1password, /run/user/1000) — the sockets are reachable (verified with raw Python socket connect), but op fails at the group credential check, not at socket access
  • Clearing proxy env vars (HTTP_PROXY, ALL_PROXY etc. that the sandbox injects) — no effect
  • OP_CONNECT_HOST or other op env vars — same failure

What works today

  • op run --env-file .env.op -- claude wrapper (one unlock, all secrets as env vars)
  • Pre-resolving specific tokens in a shell alias before launching claude
  • OP_SERVICE_ACCOUNT_TOKEN (bypasses desktop app entirely, no setgid needed)

Suggestion for Claude Code

Native op:// resolution (as proposed in this issue) would be the clean fix, since Claude Code itself runs outside the sandbox and could resolve secrets before passing them to sandboxed subprocesses. The secrets section idea from @ei-grad's comment would work well for this.

Alternatively, if the sandbox could be made to preserve the calling process's supplementary groups (or at least allow setgid binaries), op would work natively. But that's probably a harder change given bwrap's security model.

mrshu · 5 months ago

For anyone using @baelter's "pre-resolving specific tokens in a shell alias" approach -- I wrote up a detailed version of this with fish and bash examples using KeePassXC (via keepassxc-proxy-getpw): https://mareksuppa.com/til/keepassxc-claude-code-api-keys/

The gist is a shell function that fetches the key at invocation time and passes it as an inline env var prefix to claude, so it's scoped to that process only. The same pattern works with op read, bw get password, pass show, etc.

This covers the ANTHROPIC_AUTH_TOKEN / ANTHROPIC_BASE_URL case for third-party providers, and also works for MCP server secrets since MCP server processes inherit claude's environment -- just add more secrets to the wrapper. That said, it gets unwieldy with many servers, which is another argument for the native secrets section @ei-grad proposed.

yurukusa · 5 months ago

/tmp/issue-23642-comment.md

shiyw · 4 months ago

I am using @baelter's "pre-resolving specific tokens in a shell alias" approach as a workaround, but I am still seeking for a more secure solution. This approach exposes secretes to all skill scripts, MCPs (if not mannually configured), and other tools executed by Bash(). Any malicious code can easily get these secretes.

baelter · 4 months ago

Quick update: I've moved on to hooks loading credentials from a vault during skill execution, so secrets aren't sitting in the env the whole session.