DEV Community

Amit
Amit

Posted on • Originally published at artificialcuriositylabs.ai

When the Tool Doesn't Know About the Vault

TL;DR

  • op run wraps any process at launch and resolves op:// vault references into normal env vars — the tool reads os.environ["API_KEY"] and never knows 1Password exists.
  • op read belongs in code you wrote that actively manages its own credential lifecycle; op run belongs everywhere else.
  • MCP servers you didn't write get credentials without forking their code — wrap the launch command, put references in the env block, done.
  • One shell alias (alias claude='op run -- claude') covers every credential for an entire Claude Code session from a single Touch ID prompt.
  • Default to op run for anything you configure but don't write.

The fourth post in this series showed op read in code — a subprocess call that reaches into the vault at runtime and returns a secret. The pattern works. But it has a cost: the code knows about 1Password. You've baked a vault dependency into the tool.

There's a cleaner approach, and it changes what "credential-free tool" actually means.

The distinction

op read is code that fetches from the vault:

api_key = subprocess.check_output(
    ["op", "read", "op://Personal/Some-API/credential"]
).decode().strip()
Enter fullscreen mode Exit fullscreen mode

The function knows the vault exists. It imports subprocess. It constructs the op:// reference. If 1Password isn't installed, the code breaks.

op run is different. It wraps a process at launch, resolves op:// refs in the environment before the process starts, and the process reads a normal env var:

api_key = os.environ["API_KEY"]
Enter fullscreen mode Exit fullscreen mode

No subprocess. No op:// reference in the code. No vault dependency at all. From the tool's perspective, it received a credential in the environment. Where that credential came from is not its problem.

What this looks like in a real config

Three places in a working agent setup where op run replaces in-code credential fetching:

1. ~/.claude/settings.json

{
  "env": {
    "GITLAB_TOKEN": "op://Personal/GitLab-token/credential"
  }
}
Enter fullscreen mode Exit fullscreen mode

Claude Code reads GITLAB_TOKEN as a normal env var. No code in Claude Code knows about 1Password. The op:// reference sits in the config file; op run resolves it when the session starts.

2. An MCP server you didn't write

Before, wrapper code fetched its own credentials:

# wrapper.py — knew about the vault
import subprocess

def _resolve_secret(ref: str) -> str:
    return subprocess.check_output(["op", "read", ref]).decode().strip()

api_key = _resolve_secret(os.environ.get("OP_SECRET_REF"))
Enter fullscreen mode Exit fullscreen mode

After — wrap the server launch with op run and put references in the env block:

"some-mcp-server": {
  "command": "/opt/homebrew/bin/op",
  "args": ["run", "--", "/path/to/mcp-server-binary"],
  "env": {
    "API_KEY": "op://Personal/Some-API/credential"
  }
}
Enter fullscreen mode Exit fullscreen mode

The server reads os.environ["API_KEY"]. It doesn't know what populated it. You can delete the wrapper's _resolve_secret() entirely.

3. The agent launch alias

alias claude='op run -- claude'
Enter fullscreen mode Exit fullscreen mode

This wraps the entire Claude Code process. All op:// refs in settings.json env vars are resolved before the session starts. One Touch ID prompt at launch covers every credential for the entire session.

The three op tools and when to use each

Tool What it does When to use
op run Wraps a subprocess, resolves op:// refs in env at launch MCP servers, CLIs, long-running tools, any process you launch via config
op read Prints a single secret to stdout Scripts that need a value inline; code that actively manages credentials
op inject Resolves op:// refs in a template file One-time config generation — writes plaintext, delete the output after

The practical cut: op read is for code that knows it's talking to a vault. op run is for tools that shouldn't have to know.

This matters most at the MCP server layer. A server you install, configure, and run — but didn't write — has no vault dependency and never should. op run in the command wrapping that server means it gets credentials without you forking the code or adding vault support to something someone else maintains.

CI and deploy scripts

GitHub Actions can load AWS credentials the same way — store the full op:// path in a repository secret, let 1password/load-secrets-action resolve it at workflow runtime. The workflow YAML references the secret name, not the vault item ID. Same pattern as local op run: the tool sees env vars, not vault paths.

For local deploy scripts, wrap aws calls behind op run or export AWS_PROFILE from your own environment — the vault stays out of the script body.

The open question

The op run pattern works cleanly for processes you launch via config. It doesn't work for credentials that need to be rotated mid-session, or for tools that acquire tokens themselves and need the underlying secret only once to do that. For those cases, the in-code op read pattern is still the right call.

What I haven't resolved: whether there's a clean way to handle credential rotation for a long-running process that started with op run. If the token expires and the process needs to re-acquire it, the op:// reference was already resolved at launch — it doesn't re-resolve. The options are either re-launching the process (which op run handles cleanly since the env gets fresh values) or having the code fall back to op read for the renewal path. Neither is fully clean. The right answer probably depends on whether the credential is the secret or the derived token.

So what

The op run pattern is the correct default for anything you configure but don't write. An MCP server config, a CLI alias, a CI workflow, a long-running tool launched via JSON — wrap the command with op run --, put op:// references in the env block, and the tool reads a normal env var. The vault is transparent to it.

op read belongs in code you're writing that actively manages its own credential lifecycle. Everything else is a configuration problem, and op run solves it without touching the tool.

The vault stays invisible to the tool. That's the point.


This is the sixth post in a series on 1Password as infrastructure. Start from the first post.

Top comments (0)