From 1d3eeafb90c5c67b3b7300ab1c866ccc8b1d814d Mon Sep 17 00:00:00 2001 From: Amit Paz Date: Fri, 8 May 2026 13:30:04 +0300 Subject: [PATCH] fix(capture+dream): bypassPermissions for trusted subagents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After PR #48 unblocked --verbose, the next layer of capture-pipeline silence surfaced: the subagent ran successfully and the LLM correctly identified observations to save, but every mcp__lore__remember_observation call returned "Claude requested permissions to use mcp__lore__remember_observation, but you haven't granted it yet". The subagent finished PROCESSED_THROUGH_SEQ=N with zero memories persisted. Root cause: the --print subagent inherits a fresh permission state, not the parent's allowlist. Default permission mode prompts on every MCP call, and a non-interactive --print run has no way to answer. Fix: pass --permission-mode=bypassPermissions to both Popen sites (cli/commands/capture.py and cli/commands/dream.py). The capture and dream prompts are internally generated by Lore and only invoke mcp__lore__* tools (read + write own memory store), so bypassing prompts is the correct trust posture for these workers — they're not acting on user-supplied prompts and have no access to e.g. Bash. Tests in test_dreams.py and test_capture_hook.py now pin both --verbose AND --permission-mode/bypassPermissions in the args list so neither can silently regress. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lore/cli/commands/capture.py | 19 +++++++++++++++++-- src/lore/cli/commands/dream.py | 18 +++++++++++++----- tests/test_capture_hook.py | 7 +++++++ tests/test_dreams.py | 5 +++++ 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/lore/cli/commands/capture.py b/src/lore/cli/commands/capture.py index 80c0188..2806541 100644 --- a/src/lore/cli/commands/capture.py +++ b/src/lore/cli/commands/capture.py @@ -587,8 +587,23 @@ def _spawn_subagent( # without `--verbose`. Without it the subagent exits immediately # ("When using --print, --output-format=stream-json requires # --verbose") and the session buffer keeps growing without ever - # being extracted into memories — silent capture-pipeline death. - ["claude", "-p", prompt, "--output-format", "stream-json", "--verbose"], + # being extracted into memories. + # + # `--permission-mode bypassPermissions` is also required: + # without it the subagent inherits a fresh permission state + # where mcp__lore__remember_observation isn't pre-approved, so + # every save call returns "Claude requested permissions … but + # you haven't granted it yet" and the subagent finishes + # PROCESSED_THROUGH_SEQ=N without persisting any memories. + # The capture prompt is internally generated and only invokes + # mcp__lore__* tools (read + write own memory store), so + # bypassing permission prompts is the correct trust posture. + [ + "claude", "-p", prompt, + "--output-format", "stream-json", + "--verbose", + "--permission-mode", "bypassPermissions", + ], stdin=subprocess.DEVNULL, stdout=log_fh, stderr=subprocess.STDOUT, diff --git a/src/lore/cli/commands/dream.py b/src/lore/cli/commands/dream.py index 901a12f..7f68b70 100644 --- a/src/lore/cli/commands/dream.py +++ b/src/lore/cli/commands/dream.py @@ -415,11 +415,19 @@ def _spawn_subagent( log_fh = extract_log.open("a", encoding="utf-8") try: return subprocess.Popen( # noqa: S603 — internal prompt - # Claude Code 2.1.x rejects `--print --output-format stream-json` - # without `--verbose`. Without it the subagent exits immediately - # with "When using --print, --output-format=stream-json requires - # --verbose" and the buffer never gets extracted into memories. - ["claude", "-p", prompt, "--output-format", "stream-json", "--verbose"], + # See cli/commands/capture.py for why both flags are required: + # --verbose unblocks stream-json on Claude Code 2.1.x, and + # --permission-mode=bypassPermissions stops every + # mcp__lore__remember/supersede/consolidate_memories/forget + # call from being denied with "you haven't granted it yet". + # Dream is a trusted internal subagent; bypassing prompts is + # the correct trust posture. + [ + "claude", "-p", prompt, + "--output-format", "stream-json", + "--verbose", + "--permission-mode", "bypassPermissions", + ], stdin=subprocess.DEVNULL, stdout=log_fh, stderr=subprocess.STDOUT, diff --git a/tests/test_capture_hook.py b/tests/test_capture_hook.py index 84d5479..9f49b53 100644 --- a/tests/test_capture_hook.py +++ b/tests/test_capture_hook.py @@ -279,6 +279,13 @@ def __init__(self, cmd, **kwargs): assert "--output-format" in flag_args assert "stream-json" in flag_args assert "--verbose" in flag_args + # --permission-mode=bypassPermissions is also required: without it + # the subagent's mcp__lore__remember_observation calls hit "you + # haven't granted it yet" prompts (the subagent inherits a fresh + # permission state, not the parent's allowlist) and the + # extraction completes with zero memories saved. + assert "--permission-mode" in flag_args + assert "bypassPermissions" in flag_args # Detached invocation hygiene. assert captured["kwargs"]["stdin"] is subprocess.DEVNULL assert captured["kwargs"]["start_new_session"] is True diff --git a/tests/test_dreams.py b/tests/test_dreams.py index 1b9b59e..0ac3190 100644 --- a/tests/test_dreams.py +++ b/tests/test_dreams.py @@ -233,6 +233,11 @@ def __init__(self, cmd, **kwargs): assert "--output-format" in flag_args assert "stream-json" in flag_args assert "--verbose" in flag_args + # --permission-mode=bypassPermissions is also required: without it + # the subagent's mcp__lore__* save calls hit "you haven't granted + # it yet" prompts and silently produce zero memories. + assert "--permission-mode" in flag_args + assert "bypassPermissions" in flag_args # Detached invocation hygiene. assert captured["kwargs"]["stdin"] is subprocess.DEVNULL assert captured["kwargs"]["start_new_session"] is True