Skip to content

Enable enhancing tool exception context - #727

Open
DaliborKr wants to merge 2 commits into
packit:mainfrom
DaliborKr:enhance-exception-context
Open

Enable enhancing tool exception context#727
DaliborKr wants to merge 2 commits into
packit:mainfrom
DaliborKr:enhance-exception-context

Conversation

@DaliborKr

@DaliborKr DaliborKr commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Introduce additional error context for tool failures: a brief and clear error message for the LLM (to not provide unnecessarily bloated or sensitive information), and additional debugging context visible only in logs and observability tools (Phoenix, Trace Server)
  • Add tool_error_context context manager that automatically captures exception type and message alongside tool-specific context (URLs, parameters, etc.)
  • Propagate error context through MCP boundary via beeai-framework-error-context.patch (pending upstream PR - by @nforro)
  • Filter additional_context from LLM error messages in _runner.py to prevent context pollution
  • Refactor first batch of privileged tools to use tool_error_context: jira, errata, distgit, testing_farm, maintainer_rules, zstream_search

This is the first batch of tool refactoring — remaining tools will be migrated based on feedback. The context fields provided per tool are not definitive and can be extended or reduced over time as we learn what's most useful for debugging.

How it works

The first argument is a brief error message visible to the LLM. The keyword arguments are additional context visible only in logs and observability tools. See this dummy example in 'GetJiraDetailsTool':

error_handling_example

Trace server example (@nforro plans to highlight the additional context in the Trace Server later):

trace_server_example

Phoenix example:

phoenix_example

nforro and others added 2 commits August 2, 2026 23:28
Signed-off-by: Nikola Forró <nforro@redhat.com>
- Add tool_error_context context manager with automatic exception capture
- Propagate error context through MCP via beeai-framework patch
- Filter additional_context from LLM error messages in _runner.py
- Refactor first batch of privileged tools to use tool_error_context

Co-authored-by: Nikola Forró <nforro@redhat.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Dalibor Kricka <dalidalk@seznam.cz>
@qodo-for-packit

Copy link
Copy Markdown

PR Summary by Qodo

Add structured tool error context with observability-only details

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add an error-context wrapper that keeps LLM errors brief while preserving rich debug metadata.
• Propagate tool error context across the MCP boundary and into tracing/logging.
• Refactor an initial set of privileged tools to emit consistent, contextual failures.
Diagram

graph TD
  T["Privileged tools"] --> C["tool_error_context"] --> E["ToolErrorWithContext"] --> M["MCP error_context meta"] --> R["_runner.py filters context"] --> L{{"LLM"}}
  E --> O["Observability (logs/traces)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Framework-level feature (no local patching)
  • ➕ Avoids patching site-packages during image build
  • ➕ Keeps MCP error propagation behavior consistent across consumers
  • ➖ Depends on upstream release/merge timing
  • ➖ May block adoption of observability improvements in the short term
2. Tool middleware/decorator instead of manual context manager usage
  • ➕ Less repetitive refactoring across many tools
  • ➕ Harder to forget adding context in new tools
  • ➖ May be harder to pass rich, tool-specific fields without explicit call sites
  • ➖ Can obscure control flow when multiple middlewares interact
3. Split error payload into explicit fields (llm_message vs debug_context)
  • ➕ Clear contract prevents accidental leakage into prompts
  • ➕ Simplifies filtering logic in agent runner
  • ➖ Requires broader API changes across tool execution and MCP adapter types
  • ➖ More invasive change than current incremental approach

Recommendation: Current approach is a good incremental step: it improves debuggability immediately while keeping LLM-facing errors tight. Keep the temporary beeai-framework patch, but plan to drop it once upstream support lands; also consider evolving toward an explicit llm_message/debug_context split to eliminate the need for downstream filtering and reduce accidental leakage risk.

Files changed (16) +311 / -215

Enhancement (4) +79 / -0
observability.pyLog and export tool additional_context to tracing spans +29/-0

Log and export tool additional_context to tracing spans

• Registers an emitter listener for tool error events that extracts additional_context (directly or via MCP-propagated context). Logs the context and sets span attributes (metadata.*) for Phoenix/trace-server filtering.

ymir/agents/observability.py

base.pyAdd tool_error_context context manager for consistent error wrapping +17/-0

Add tool_error_context context manager for consistent error wrapping

• Introduces tool_error_context to capture exception type/message and attach tool-specific debug fields. Converts unexpected exceptions into ToolErrorWithContext while preserving the original exception as cause.

ymir/tools/base.py

errors.pyIntroduce ToolErrorWithContext to carry observability metadata safely +28/-0

Introduce ToolErrorWithContext to carry observability metadata safely

• Adds a ToolError subclass that stores additional_context and merges it into ToolError.context under a dedicated key. Keeps additional_context available for logs/traces without relying on LLM rendering.

ymir/tools/errors.py

gateway_utils.pyLog additional_context (redacted) on tool failures +5/-0

Log additional_context (redacted) on tool failures

• Extends existing tool error logging to include additional_context when present. Applies credential redaction before writing additional context to logs.

ymir/tools/gateway_utils.py

Bug fix (1) +14 / -3
_runner.pyFilter observability-only context from LLM tool error prompts +14/-3

Filter observability-only context from LLM tool error prompts

• Temporarily removes the additional_context key from ToolError.context before calling explain(), then restores it. Prevents debug context from polluting the LLM-visible error message.

ymir/agents/reasoning_agent/_runner.py

Refactor (7) +159 / -211
distgit.pyRefactor distgit tool failures to include structured context +70/-54

Refactor distgit tool failures to include structured context

• Wraps major failure points (remote check, cloning, build selection, push, mirror polling) in tool_error_context with package/branch/ref/URL fields. Also shifts some errors to concise ToolError messages and adds targeted logging for push/mirror failures.

ymir/tools/privileged/distgit.py

errata.pyRefactor errata tool failures to include structured context +12/-31

Refactor errata tool failures to include structured context

• Replaces repeated try/except ToolError wrapping with tool_error_context blocks around API calls. Standardizes concise error messages while preserving debug context for observability.

ymir/tools/privileged/errata.py

gateway.pyRemove duplicate credential redaction implementation +0/-31

Remove duplicate credential redaction implementation

• Deletes the local redact patterns and helper used for logging. Centralizes redaction behavior in shared utilities (gateway_utils).

ymir/tools/privileged/gateway.py

jira.pyRefactor Jira privileged tools to use tool_error_context +43/-61

Refactor Jira privileged tools to use tool_error_context

• Wraps key Jira HTTP interactions in tool_error_context with relevant fields (URLs, JQL, labels, filenames, transition targets). Keeps one non-critical remote-links fetch as best-effort with debug logging instead of failing the tool.

ymir/tools/privileged/jira.py

maintainer_rules.pyAdd contextual errors for maintainer rules fetches +20/-18

Add contextual errors for maintainer rules fetches

• Wraps the GitLab rules fetch flow in tool_error_context and provides package/file_path context on failures. Keeps a dedicated timeout ToolError message, now including the package name.

ymir/tools/privileged/maintainer_rules.py

testing_farm.pyWrap Testing Farm tool failures with reproduction context +5/-7

Wrap Testing Farm tool failures with reproduction context

• Adds tool_error_context around Testing Farm request fetch and reproduction flows, including build_nvr where relevant. Removes the previous broad try/except in favor of centralized wrapping.

ymir/tools/privileged/testing_farm.py

zstream_search.pyAdd contextual errors for z-stream eligibility and Jira search +9/-9

Add contextual errors for z-stream eligibility and Jira search

• Wraps older-zstream checks and Jira search execution in tool_error_context, attaching component/fix_version/JQL context. Relies on the centralized wrapper instead of local exception translation blocks.

ymir/tools/privileged/zstream_search.py

Other (4) +59 / -1
Containerfile.c10sApply beeai-framework error-context patch during image build +2/-0

Apply beeai-framework error-context patch during image build

• Copies a new beeai-framework patch into the build context and applies it in site-packages. Ensures MCP error metadata propagation is available in the container image.

Containerfile.c10s

Containerfile.c9sApply beeai-framework error-context patch during image build (py3.11 venv) +2/-0

Apply beeai-framework error-context patch during image build (py3.11 venv)

• Adds the patch to the image build and applies it inside the Python 3.11 virtualenv site-packages. Aligns error-context behavior across c9s images.

Containerfile.c9s

Containerfile.mcpApply beeai-framework error-context patch in MCP container +5/-1

Apply beeai-framework error-context patch in MCP container

• Copies and applies the patch after installing built wheels. Ensures the MCP runtime preserves tool error metadata.

Containerfile.mcp

beeai-framework-error-context.patchPatch beeai-framework MCP to preserve error context metadata +50/-0

Patch beeai-framework MCP to preserve error context metadata

• Wraps MCP tool execution to return isError results with _meta.error_context when exceptions occur. Updates MCPTool to re-raise ToolError with the propagated context dict when result.isError is set.

beeai-framework-error-context.patch

@qodo-for-packit

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (2) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 7 rules

Grey Divider


Action required

1. Unredacted context leaks secrets 🐞 Bug ⛨ Security
Description
_setup_tool_error_logging() logs tool additional_context verbatim and exports it as span
attributes, but tool_error_context() also injects raw exception strings into that context. This
can persist credentials/tokens into logs and OpenTelemetry traces (e.g., GitLab URLs containing
GITLAB_TOKEN).
Code

ymir/agents/observability.py[R106-110]

+            logger.error(f"Tool {meta.creator} additional context: {additional_context}")
+            span = trace_api.get_current_span()
+            if span.is_recording():
+                for key, value in additional_context.items():
+                    span.set_attribute(f"metadata.{key}", str(value))
Relevance

●●● Strong

Missing redaction in tool error logging treated as security regression and fixed (PR #414). Similar
leak via spans/logs likely blocked.

PR-#414
PR-#391

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new observability hook logs additional_context directly and exports each value to the active
span, while tool_error_context always captures the raw exception string into additional_context.
One refactored tool (CreateZstreamBranchTool) constructs a tokenized GitLab URL inside a
tool_error_context region, demonstrating that secrets can enter exception strings and then be
persisted into logs/traces. The repo already has a redaction utility used in gateway logging,
highlighting that these sinks are expected to be redacted.

ymir/agents/observability.py[93-112]
ymir/tools/base.py[13-22]
ymir/tools/privileged/distgit.py[185-196]
ymir/tools/gateway_utils.py[16-61]
PR-#414

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ymir/agents/observability.py:_setup_tool_error_logging()` logs and exports `additional_context` without any credential redaction, and `ymir/tools/base.py:tool_error_context()` always adds `additional_context["exception"] = f"{type(e).__name__}: {e}"`.

This combination can emit secrets into logs and into OpenTelemetry span attributes (which are typically indexed/searchable), because exception strings and context values may contain tokens/credentials.

## Issue Context
The repo already has a shared `redact_credentials()` helper used by MCP gateways, but the new agent-side observability hook bypasses it. Also, some tools construct credential-bearing URLs (e.g., `https://oauth2:*******@gitlab.com/...`) inside `tool_error_context()`, so any exception that includes the URL/argv can leak tokens via the captured exception string.

## Fix Focus Areas
- ymir/agents/observability.py[93-112]
- ymir/tools/base.py[13-22]
- ymir/tools/gateway_utils.py[16-61]
- ymir/tools/privileged/distgit.py[190-196]

## Suggested fix
1) In `_setup_tool_error_logging()`:
  - Apply `redact_credentials()` to the string representation of `additional_context` before logging.
  - When setting span attributes, redact values (and consider allowlisting keys) before `span.set_attribute(...)`.
2) In `tool_error_context()`:
  - Avoid capturing raw exception messages verbatim, or redact them before storing (e.g., store only exception type, or a redacted/shortened message).
3) Add/adjust tests or a small unit-level check to ensure known token patterns are redacted in both logs and span attributes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. MCP error echoes exception 🐞 Bug ⛨ Security
Description
beeai-framework-error-context.patch constructs the MCP tool error content using the raw exception
string ({e}), which becomes LLM-visible tool output. This can expose sensitive details
(tokens/URLs/paths) and undermines the PR goal of keeping model-facing errors brief and
non-sensitive.
Code

beeai-framework-error-context.patch[R13-18]

++            error_context = getattr(e, "context", None) or {}
++            meta = {"error_context": error_context} if error_context else None
++            return MCPCallToolResult(
++                content=[MCPTextContent(type="text", text=f"Error executing tool {tool.name}: {e}")],
++                isError=True,
++                _meta=meta,
Relevance

●● Moderate

Logs/traces redaction enforced (PR #414), but no clear precedent for stripping exception text from
LLM-visible MCP errors.

PR-#414

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The patch explicitly formats the tool result text with {e}. Separately, refactored code shows
credential-bearing URLs can exist during tool execution (e.g., GitLab URL with
oauth2:{token}@...), which can end up in exception strings and thus become model-visible through
this MCP error path.

beeai-framework-error-context.patch[10-19]
ymir/tools/privileged/distgit.py[190-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The MCP boundary patch returns `MCPTextContent(... text=f"Error executing tool {tool.name}: {e}")`, directly interpolating the exception string into the tool result content.

Because this content is what the agent/model receives for tool failures, any sensitive data present in exception messages can be exposed to the LLM.

## Issue Context
Some tools construct credential-bearing URLs (e.g., `https://oauth2:*******@gitlab.com/...`) or may raise exceptions containing request/command details. Even if most tools are migrated to raise sanitized `ToolErrorWithContext`, this patch is a catch-all for *any* exception, including unwrapped ones.

## Fix Focus Areas
- beeai-framework-error-context.patch[10-19]

## Suggested fix
Update the patch so the MCP `content` uses a safe, fixed message (or a sanitized/short error message) and does **not** include `str(e)`.

Example approach inside the patched `except`:
- Set `text=f"Error executing tool {tool.name}"` (optionally include only `type(e).__name__`).
- Keep detailed debugging context in `_meta` / `error_context` only (and ensure that context values are safe/serializable/redacted as needed).

This preserves debuggability via observability while preventing sensitive exception strings from reaching the LLM.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. distgit tests not updated 📘 Rule violation ▣ Testability
Description
CreateZstreamBranchTool now raises ToolError("Push to dist-git was rejected"), but the unit test
still asserts the old error text (match="Push rejected"), indicating tests were not updated to
cover the changed behavior in privileged tools.
Code

ymir/tools/privileged/distgit.py[R249-251]

+                    if info.flags & git.remote.PushInfo.ERROR:
+                        logger.error("Push to dist-git rejected: %s", info.summary.strip())
+                        raise ToolError("Push to dist-git was rejected")
Relevance

●●● Strong

distgit behavior changes usually paired with unit test updates; PR #495 added tests for push
rejection paths.

PR-#495
PR-#663

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1589 requires updated unit tests when privileged tools change. The updated tool code raises a
different error message on push rejection, while the existing unit test still matches the previous
message, demonstrating the test suite was not updated to cover the new behavior.

Rule 1589: Require unit tests for changes to privileged tools (ymir/tools/privileged/, esp. distgit.py)
ymir/tools/privileged/distgit.py[246-251]
ymir/tools/privileged/tests/unit/test_distgit.py[110-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Privileged tool behavior in `ymir/tools/privileged/distgit.py` changed, but unit tests were not updated to assert the new error behavior/message.

## Issue Context
Compliance requires unit tests to be added/updated when modifying privileged tools under `ymir/tools/privileged/`, especially for error paths.

## Fix Focus Areas
- ymir/tools/privileged/distgit.py[246-251]
- ymir/tools/privileged/tests/unit/test_distgit.py[110-156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Containerfile.c10s non-jotnar base image 📘 Rule violation § Compliance
Description
Multiple changed Containerfiles (Containerfile.c10s, Containerfile.c9s, and Containerfile.mcp)
use base images that do not start with quay.io/jotnar/, violating the required image namespace
policy. Their FROM instructions must be updated to reference images under the approved
quay.io/jotnar/ namespace.
Code

Containerfile.c10s[72]

+COPY beeai-framework-error-context.patch /tmp
Relevance

● Weak

Repo uses upstream base images (e.g., fedora:43 in PR #592); no evidence enforcing quay.io/jotnar
for FROM.

PR-#592
PR-#471

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1591 requires that all FROM instructions in changed Containerfiles reference images under
quay.io/jotnar/. The cited files violate this by using non-compliant base images:
Containerfile.c10s uses quay.io/centos/centos:stream10, Containerfile.c9s uses
quay.io/centos/centos:stream9, and Containerfile.mcp uses fedora:43, none of which begin with
quay.io/jotnar/.

Rule 1591: Enforce quay.io/jotnar namespace for container images
Containerfile.c10s[1-1]
Containerfile.c9s[1-1]
Containerfile.mcp[1-1]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Changed Containerfiles must use base images from the `quay.io/jotnar/` namespace.

## Issue Context
Rule 1591 requires that all `FROM` instructions in changed Containerfiles reference images under `quay.io/jotnar/`. The current `FROM` lines reference non-compliant images: `Containerfile.c10s` uses `quay.io/centos/centos:stream10`, `Containerfile.c9s` uses `quay.io/centos/centos:stream9`, and `Containerfile.mcp` uses `fedora:43`.

## Fix Focus Areas
- Containerfile.c10s[1-1]
- Containerfile.c9s[1-1]
- Containerfile.mcp[1-1]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +249 to +251
if info.flags & git.remote.PushInfo.ERROR:
logger.error("Push to dist-git rejected: %s", info.summary.strip())
raise ToolError("Push to dist-git was rejected")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. distgit tests not updated 📘 Rule violation ▣ Testability

CreateZstreamBranchTool now raises ToolError("Push to dist-git was rejected"), but the unit test
still asserts the old error text (match="Push rejected"), indicating tests were not updated to
cover the changed behavior in privileged tools.
Agent Prompt
## Issue description
Privileged tool behavior in `ymir/tools/privileged/distgit.py` changed, but unit tests were not updated to assert the new error behavior/message.

## Issue Context
Compliance requires unit tests to be added/updated when modifying privileged tools under `ymir/tools/privileged/`, especially for error paths.

## Fix Focus Areas
- ymir/tools/privileged/distgit.py[246-251]
- ymir/tools/privileged/tests/unit/test_distgit.py[110-156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +106 to +110
logger.error(f"Tool {meta.creator} additional context: {additional_context}")
span = trace_api.get_current_span()
if span.is_recording():
for key, value in additional_context.items():
span.set_attribute(f"metadata.{key}", str(value))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Unredacted context leaks secrets 🐞 Bug ⛨ Security

_setup_tool_error_logging() logs tool additional_context verbatim and exports it as span
attributes, but tool_error_context() also injects raw exception strings into that context. This
can persist credentials/tokens into logs and OpenTelemetry traces (e.g., GitLab URLs containing
GITLAB_TOKEN).
Agent Prompt
## Issue description
`ymir/agents/observability.py:_setup_tool_error_logging()` logs and exports `additional_context` without any credential redaction, and `ymir/tools/base.py:tool_error_context()` always adds `additional_context["exception"] = f"{type(e).__name__}: {e}"`.

This combination can emit secrets into logs and into OpenTelemetry span attributes (which are typically indexed/searchable), because exception strings and context values may contain tokens/credentials.

## Issue Context
The repo already has a shared `redact_credentials()` helper used by MCP gateways, but the new agent-side observability hook bypasses it. Also, some tools construct credential-bearing URLs (e.g., `https://oauth2:*******@gitlab.com/...`) inside `tool_error_context()`, so any exception that includes the URL/argv can leak tokens via the captured exception string.

## Fix Focus Areas
- ymir/agents/observability.py[93-112]
- ymir/tools/base.py[13-22]
- ymir/tools/gateway_utils.py[16-61]
- ymir/tools/privileged/distgit.py[190-196]

## Suggested fix
1) In `_setup_tool_error_logging()`:
   - Apply `redact_credentials()` to the string representation of `additional_context` before logging.
   - When setting span attributes, redact values (and consider allowlisting keys) before `span.set_attribute(...)`.
2) In `tool_error_context()`:
   - Avoid capturing raw exception messages verbatim, or redact them before storing (e.g., store only exception type, or a redacted/shortened message).
3) Add/adjust tests or a small unit-level check to ensure known token patterns are redacted in both logs and span attributes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +13 to +18
+ error_context = getattr(e, "context", None) or {}
+ meta = {"error_context": error_context} if error_context else None
+ return MCPCallToolResult(
+ content=[MCPTextContent(type="text", text=f"Error executing tool {tool.name}: {e}")],
+ isError=True,
+ _meta=meta,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Mcp error echoes exception 🐞 Bug ⛨ Security

beeai-framework-error-context.patch constructs the MCP tool error content using the raw exception
string ({e}), which becomes LLM-visible tool output. This can expose sensitive details
(tokens/URLs/paths) and undermines the PR goal of keeping model-facing errors brief and
non-sensitive.
Agent Prompt
## Issue description
The MCP boundary patch returns `MCPTextContent(... text=f"Error executing tool {tool.name}: {e}")`, directly interpolating the exception string into the tool result content.

Because this content is what the agent/model receives for tool failures, any sensitive data present in exception messages can be exposed to the LLM.

## Issue Context
Some tools construct credential-bearing URLs (e.g., `https://oauth2:*******@gitlab.com/...`) or may raise exceptions containing request/command details. Even if most tools are migrated to raise sanitized `ToolErrorWithContext`, this patch is a catch-all for *any* exception, including unwrapped ones.

## Fix Focus Areas
- beeai-framework-error-context.patch[10-19]

## Suggested fix
Update the patch so the MCP `content` uses a safe, fixed message (or a sanitized/short error message) and does **not** include `str(e)`.

Example approach inside the patched `except`:
- Set `text=f"Error executing tool {tool.name}"` (optionally include only `type(e).__name__`).
- Keep detailed debugging context in `_meta` / `error_context` only (and ensure that context values are safe/serializable/redacted as needed).

This preserves debuggability via observability while preventing sensitive exception strings from reaching the LLM.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@DaliborKr DaliborKr changed the title Enhance exception context Enable enhancing tool exception context Aug 2, 2026
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