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

mnbf9rca3 minFix

To stop my API keys being sucked up by some random malware, I keep secrets in 1Password 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.

{
  "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 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 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.

No comments yet