| name | op-exec |
| description | Use this skill when the user asks about op-exec, running commands with 1Password secrets injected, wrapping processes with secret injection, automating secret-aware command execution, or configuring whole-item environment injection with multiple output targets. op-exec is a wrapper around the 1Password CLI that simplifies running commands with secrets from 1Password vaults.
|
op-exec - 1Password Secret Injection Wrapper
op-exec (from nsheaps/op-exec) is a
convenience wrapper around op run that simplifies injecting 1Password secrets
into command execution.
Overview
op-exec fetches all STRING and CONCEALED fields from a 1Password item, converts
field labels to environment variable names (UPPER_SNAKE_CASE), recursively
resolves any op:// references in field values (max depth 5), and either
exports them for a command or prints export statements for sourcing.
Installation
Via mise (recommended)
[tools]
"github:nsheaps/op-exec" = "latest"
Via Homebrew
brew install nsheaps/tap/op-exec
Via GitHub releases
curl -fsSL "https://github.com/nsheaps/op-exec/releases/download/v${VERSION}/op-exec-linux-amd64" -o op-exec
chmod +x op-exec
Usage
op-exec op://vault/item -- command [args...]
op-exec op://vault/item
eval "$(op-exec op://vault/item)"
Prerequisites
- 1Password CLI (
op) must be installed and authenticated
- Either signed in interactively or via
OP_SERVICE_ACCOUNT_TOKEN
Common Patterns
Expose an Entire Item as Environment
op-exec op://MyVault/ENVIRONMENT -- npm start
Run a Script with Secrets
op-exec op://Development/my-app-config -- ./deploy.sh
Use with Docker
op-exec op://Work/docker-secrets -- docker compose up
CI/CD Integration
export OP_SERVICE_ACCOUNT_TOKEN="ops_..."
op-exec op://Automation/deploy-config -- ./ci-script.sh
Recursive Resolution
Field values that are themselves op:// references are resolved automatically:
# Item "ENVIRONMENT" in vault "MyVault":
# API_KEY = op://MyVault/api-credentials/key ← resolved recursively
# DB_HOST = prod.db.example.com ← used as-is
Plugin Settings — Automatic Session Injection
The 1pass plugin can automatically run op-exec at session start and write
the resolved environment variables to multiple targets.
Configuration
1pass:
installOpExec: true
opExecVersion: "latest"
opExec:
items:
- "op://MyVault/ENVIRONMENT"
- "op://MyVault/extra-secrets"
targets:
- sessionStartBashEnv
- envLocal
- userSettings
envLocal:
Output Targets
| Target | Mechanism | Scope | Persistence | Non-Bash tools |
|---|
sessionStartBashEnv | CLAUDE_ENV_FILE | Bash tool calls | Session only | No |
envLocal | $AGENT_HOME_DIR/.env.local (shell-sourceable export K=v lines) | All tools sourcing the file (e.g. direnv) | Across sessions (idempotent replace-or-append) | Yes — when sourced by the consumer |
userSettings | ~/.claude/settings.local.json .env | All tools | Across sessions | Yes |
Default: Both sessionStartBashEnv and userSettings are enabled when
targets is not specified, ensuring env vars are available to all tools and
also in bash sessions. envLocal is opt-in.
envLocal target details
- Writes shell-sourceable
export KEY=value lines to envLocal.path
(default $AGENT_HOME_DIR/.env.local, fallback $CLAUDE_PROJECT_DIR/.env.local).
- Uses idempotent replace-or-append semantics (via shared-lib's
env_file_upsert_export). The file is NOT truncated on session start, so
vars from other sources (manual edits, other plugins) survive.
- On first write of the session, also adds
source <envLocal.sourceChain> to
CLAUDE_ENV_FILE. Default sourceChain is $AGENT_HOME_DIR/.env (allowing a
repo-templated .env to source .env.local so direnv and other consumers
pick up the vars). Pass sourceChain: self to source the envLocal file
directly from CLAUDE_ENV_FILE, or sourceChain: none (alias: false) to
skip the source line entirely.
- The agent repo is responsible for
.gitignore'ing .env.local (and .env
if applicable) and for setting up the consumer-side source of the file.
When to Use Which Target
- sessionStartBashEnv only: Secrets that should not persist on disk beyond
the session, or when you only need them in bash tool calls.
- envLocal: When non-Claude-Code processes need the vars (e.g. direnv,
scripts run from a shell). Combines well with a repo-templated
.env that
sources .env.local for the consumer side.
- userSettings only: Config that non-Bash tools (MCP servers, etc.) need,
or values that should persist across sessions.
- Both
sessionStartBashEnv + userSettings (default): Most common —
ensures secrets are available everywhere during the session and to all
tool types. Add envLocal on top if you also want them in a sourceable
file on disk.
Setup Hook (Install-Time Write)
When envLocal is included in opExec.targets, the plugin also registers a
Setup{init} hook that fires at session bootstrap, before the first
interactive SessionStart — specifically during the claude --init-only
pre-pass that the agent launcher runs on every startup. This ensures
$AGENT_HOME_DIR/.env.local is populated before the agent's Claude session
starts, so launchers can source it from the pre-session environment.
How it works:
- On session bootstrap, the launcher runs
claude --init-only; this triggers
the Setup{init} hook, which runs op-exec-env-setup.sh.
- The script resolves all
opExec.items via op-exec and upserts each
resolved export KEY=value line into $AGENT_HOME_DIR/.env.local via
env_file_upsert_export (same semantics as the SessionStart sibling — no
wholesale replacement; non-1pass entries are preserved).
- On every subsequent
SessionStart, op-exec-env.sh re-resolves and upserts
again (idempotent — values that haven't changed are no-ops).
AGENT_HOME_DIR gating: If AGENT_HOME_DIR is not set in the hook
environment and no envLocal.path is configured, the Setup hook logs a notice
and exits cleanly. The SessionStart hook will write .env.local once
AGENT_HOME_DIR is available in the session env.
op / op-exec availability: If either binary is missing at Setup time (e.g.
autoInstall has not yet run), the Setup hook exits cleanly. The SessionStart
hook always runs the install step before the env-injection step, so the binaries
will be available by the time SessionStart fires.
ENVIRONMENT Aggregator Pattern
The ENVIRONMENT item in 1Password (e.g., op://AI-Jack/ENVIRONMENT) serves as the
canonical aggregator for all environment variables. Instead of adding separate items to
opExec.items, add new secrets as fields to the ENVIRONMENT item:
- In 1Password, add a new field to the ENVIRONMENT item with the desired env var name
as the label (e.g.,
DISCORD_BOT_TOKEN)
- Set the field value to an
op:// reference pointing to the actual secret
(e.g., op://AI-Jack/discord--jack_oat_bot/token)
- op-exec resolves references recursively, so the field value will be the actual secret
at runtime
- The field label becomes the exported env var name (converted to UPPER_SNAKE_CASE)
This pattern means you only need one item in opExec.items (the ENVIRONMENT item) to
manage all secrets. Adding separate items should be avoided unless the secret doesn't
fit the aggregator pattern.
Example
ENVIRONMENT item fields:
- TELEGRAM_BOT_TOKEN = op://AI-Jack/telegram-bot/token
- DISCORD_BOT_TOKEN = op://AI-Jack/discord--jack_oat_bot/token
- BRAINTRUST_API_KEY = op://AI-Jack/braintrust/api-key
Plugin Config with Aggregator
When using the aggregator pattern, the plugin config is minimal — just one item:
1pass:
opExec:
items:
- "op://AI-Jack/ENVIRONMENT"
All env vars are managed by adding/removing fields on that single 1Password item,
rather than editing plugin configuration.
Troubleshooting
"op-exec: command not found"
Install via mise, Homebrew, or enable auto-install in the plugin settings.
Secrets not resolving
Ensure op is authenticated:
op whoami
Permission denied
Ensure the service account or user has access to the referenced vault.
No fields exported
op-exec only exports STRING and CONCEALED field types. Other field types
(sections, OTP, etc.) are skipped. Verify the item has the expected fields:
op item get "ENVIRONMENT" --vault "MyVault" --format json | jq '.fields[] | {label, type}'