Skip to content

fix: query_project rejected read-only tools excluded by the current context#1527

Closed
bor wants to merge 1 commit into
oraios:mainfrom
bor:fix/query-project-forward-context-excluded-tools
Closed

fix: query_project rejected read-only tools excluded by the current context#1527
bor wants to merge 1 commit into
oraios:mainfrom
bor:fix/query-project-forward-context-excluded-tools

Conversation

@bor
Copy link
Copy Markdown
Contributor

@bor bor commented May 29, 2026

Summary

QueryProjectTool.apply gated forwarding on tool.is_active(), which reflects the caller's active toolset (context + global config + modes + active project). A read-only tool excluded merely by the current context — e.g. read_file, find_file, list_dir, or search_for_pattern in CLI/IDE contexts such as claude-code, codex, copilot-cli, vscode, junie, antigravity, jb-*, ide — was therefore blocked from being forwarded to another registered project, even though the tool is read-only and perfectly runnable against the target. The error users saw was Tool <name> is not active.

Root cause

Tool.is_active() -> agent.tool_is_active(name) -> membership in agent._active_tools, which is the base toolset reduced by context + global config + modes + project. Context-level exclusions (client-cosmetic, e.g. Claude Code has a native grep so search_for_pattern is excluded for UX) were therefore conflated with "tool may not run at all." Forwarding is a different question — context-level exclusions should not block it.

Corroboration: the existing server-side executor ProjectServer._query_project already gates on only is_readonly(), so read-only is the intended gate; the over-strict check existed only on the caller side.

Fix

  • Replace assert tool.is_active() in QueryProjectTool.apply with a check against the global Serena configuration only, via a new ToolInclusionDefinition.disables_tool(name) helper that unifies excluded_tools (incremental mode) and fixed_tools (fixed mode) handling.
  • Keep the is_readonly() requirement.
  • All three gates in apply() (read-only, global-config, registration) now raise explicit ValueErrors rather than asserts, so they cannot be stripped by python -O.

After the fix:

  • Context-level (client-cosmetic) exclusions no longer block forwarding. ✓ (the reported bug)
  • Globally disabled tools still are. ✓
  • Non-read-only tools still are. ✓

Scope

Intentionally minimal — fixes only the reported behavior. ProjectServer._query_project is unchanged; it already gates on is_readonly(), which remains correct.

Test plan

  • test/serena/test_query_project_tools.py — 4 in-process integration tests (no language server required; search_for_pattern is non-symbolic + read-only, so _is_project_server_required returns False):
    • happy path: context-excluded read-only tool forwards successfully (this fails on main with AssertionError: Tool search_for_pattern is not active.)
    • read-only gate: forwarding create_text_file raises ValueError matching "read-only"
    • global-config gate: serena_config.excluded_tools blocks forwarding with ValueError matching "global"
    • registration gate: unknown project_name raises ValueError matching "not registered"
    • Rejection tests pin to ValueError (not AssertionError) so a regression back to assert would fail loudly.
  • test/serena/config/test_serena_config.py::TestToolInclusionDefinitionDisablesTool — 3 unit tests covering default / incremental / fixed modes of the new helper.
  • poe format clean.
  • poe type-check clean.
  • CHANGELOG entry added under # Unreleased (main) / * Tools:.

…ontext

The forwarding gate in QueryProjectTool.apply asserted tool.is_active(), which
reflects the caller's active toolset (context + global config + modes + active
project). A tool excluded merely by the current context -- e.g. search_for_pattern
in the claude-code context, where the client has a native grep -- was therefore
blocked, even though it is read-only and valid against the target project.

Replace the is_active() gate with a check against the global Serena configuration
only, keeping the read-only requirement. Context-level (client-cosmetic) exclusions
no longer block forwarding; globally disabled tools still do. The shared
ToolInclusionDefinition.disables_tool helper unifies fixed and incremental mode
handling. Policy and safety gates now raise explicit ValueErrors rather than
asserts so they cannot be stripped via python -O.
Copy link
Copy Markdown
Contributor

@opcode81 opcode81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your PR, but I will need to reject this.

  • As indicated in my comment, the new logic that is introduced does not make sense.
  • There is no point in replacing AssertionErrors with ValueErrors.
  • We do not need tests for this.
  • The simplest approach is simply to remove the assertion that the tool must be active (noting that there are limitations regarding LLM awareness). You are welcome to propose a new PR with this one-line change plus changelog entry.

Comment on lines +56 to +64
# explicit ValueError (not assert): safety/policy gates must not be strippable via `python -O`.
if not tool.is_readonly():
raise ValueError(f"Tool {tool_name} is not read-only and cannot be executed in another project.")
# the previous `is_active()` check conflated client-cosmetic context exclusions
# (e.g. search_for_pattern in the claude-code context, where the client has a native grep)
# with intentional global disabling. Forwarding a read-only tool to another project is
# valid even if the current context hides it; only the global config acts as kill-switch.
if self.agent.serena_config.disables_tool(tool_name):
raise ValueError(f"Tool {tool_name} is disabled in the global Serena configuration and cannot be executed.")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The introduction of the new method does not make sense: Whether or not SerenaConfig disables a tool is meaningless.

It can make sense to allow tools that are not currently enabled, but there is an important limitation: The LLM will be aware of their existence (and know how to use them) only if they are exposed by the MCP server.
So removing the is_active condition will have a practical effect only if the tool was previously exposed but not enabled.
In all other cases, you would need to manually inform the LLM about these tools.

There is no need to add any additional condition if we just want to soften the condition that needs to apply.

@bor
Copy link
Copy Markdown
Contributor Author

bor commented Jun 2, 2026

Thanks for the thorough review and the clear guidance, @opcode81 - much appreciated. Closing this PR; I'll open a fresh one with just the minimal change (drop the is_active() assertion) plus a changelog entry, as you suggested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants