# Fix: Claude Code with op run responds "No deferred tool marker found in the resumed session"

https://blog.cynexia.com/running-claude-code-under-op-run-breaks-resume

Author: [Rob Aleck](https://blog.cynexia.com/pages/about) · https://github.com/mnbf9rca · https://www.linkedin.com/in/robertaleck/ · https://family-foqos.app/

Published: 2026-09-04
Updated: 2026-10-01
Categories: Fix


To stop my API keys being sucked up by some random malware, I [keep secrets in 1Password](<https://blog.cynexia.com/updated-using-1password-and-direnv-to-store-developer-secrets>) and inject them into commands at runtime with `op run`. That works for almost everything, but when using Claude Code, i started getting a confusing error message.

## Injecting MCP API keys to Claude Code

Claude Code reads MCP server config from `.mcp.json` and expands `${VAR}` references from the environment.

```json
{
  "mcpServers": {
    "trmnl": {
      "type": "http",
      "url": "https://mcp-server.com/mcp?api_key=${MY_SECRET_MCP_API_KEY}"
    }
  }
}
```

The API key is in a file. `.env.tpl`:

```
MY_SECRET_MCP_API_KEY=op://vault/item/mcp_api_key
```

To give Claude the API key without exporting it, I call it via `op run`, but when attempting to resume a session, i got this:

```
> op run --env-file=.env.tpl -- claude --resume <session-id>
Error: No deferred tool marker found in the resumed session. Either the session was not deferred, the marker is stale (tool already ran), or it exceeds the tail-scan window. Provide a prompt to continue the conversation.
```

 Nothing in that message mentions a terminal tells me what’s happening. Is this 1Password? I found another report with [same root cause](<https://github.com/anthropics/claude-code/issues/6820>) and a different message: `Input must be provided either through stdin or as a prompt argument when using --print`, which is even more misleading because you never passed `--print`.

## `op run` masks secrets through stdout

`op run` [masks secrets in the child's output](<https://www.1password.dev/cli/reference/commands/run#examples>) by default. To do that it has to read the child's stdout through a pipe, meaning a child process no longer sees a terminal on stdout.

Claude Code treats a non-terminal stdout as a signal that it is being scripted (e.g. in CI), and switches to print mode. In print mode, `--resume` without a prompt only works for a session that was paused on a deferred tool. A normal session was not, so it prints that error and exits. You can confirm the mechanism without Claude:

```
❯ op run --env-file=.env.tpl -- sh -c '[ -t 1 ] && echo tty || echo pipe'
pipe
```

Note that this only happens when the env file resolves at least one secret. With an empty env file there is nothing to mask, `op run` skips the pipe, and the test passes.

## Turn off masking to fix Claude

The fix is simple - turn masking off with the `--no-masking` argument:

```
op run --no-masking --env-file=.env.tpl -- claude --resume <session-id>
```

Run the same check and stdout is a terminal again:

```
❯ op run --no-masking --env-file=.env.tpl -- sh -c '[ -t 1 ] && echo tty || echo pipe'
tty
```

Masking is a plaintext-match backstop that cannot work on an interactive terminal UI anyway. So that i remember how to launch Claude, i have a simple wrapper script in the repo's `bin/`, added to `PATH` by direnv, so `op_claude` is the only thing I type:

```
#!/bin/sh
cd "$(dirname "$0")/.." || exit 1
exec op run --no-masking --env-file=.env.tpl -- claude "$@"
```

## If you would rather not use `op run` at all

The narrowest option resolves one secret with `op read` and replaces the shell with Claude in the same process, so there is no pipe and no extra process:

```
MY_KEY="$(op read 'op://vault/item/field')" exec claude "$@"
```

That also limits Claude, and every subprocess it spawns, to the single secret it needs.

