BUG-HUNT: [error-handling] PlanExecutor stores full traceback.format_exc() in plan.error_details["traceback"] — internal stack frames exposed via plan status --format json #6637

Open
opened 2026-04-09 22:38:28 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: [error-handling] — Full traceback stored in plan error_details, exposed through public API

Severity Assessment

  • Impact: Internal stack frames, file paths, and potentially sensitive variable names are written into plan.error_details["traceback"] and stored in the database. Any caller reading agents plan status <id> --format json or agents plan errors <id> --format json receives a verbatim Python traceback.
  • Likelihood: Triggers on every Strategize or Execute phase failure — i.e. the normal error path.
  • Priority: High

Location

  • File: src/cleveragents/application/services/plan_executor.py
  • Function: PlanExecutor.run_strategize, PlanExecutor._run_execute_with_runtime, PlanExecutor._run_execute_with_stub
  • Lines: ~748–752, ~914–919, ~1034–1038

Description

In all three execution paths (run_strategize, _run_execute_with_runtime, _run_execute_with_stub), when an exception is caught the full Python traceback is written directly into plan.error_details["traceback"] and then committed to the database via _commit_plan:

# run_strategize (lines 748-752)
plan.error_details = {
    "exception_type": type(exc).__name__,
    "traceback": traceback.format_exc(),   # ← full stack trace
}
self._lifecycle._commit_plan(plan)

# _run_execute_with_runtime (lines 914-919)
plan.error_details = {
    "exception_type": type(exc).__name__,
    "traceback": traceback.format_exc(),   # ← full stack trace
    "mode": "runtime",
}
self._lifecycle._commit_plan(plan)

# _run_execute_with_stub (lines 1034-1038)
plan.error_details = {
    "exception_type": type(exc).__name__,
    "traceback": traceback.format_exc(),   # ← full stack trace
    "mode": "stub",
}
self._lifecycle._commit_plan(plan)

This error_details dict is later serialized and returned in:

  • agents plan status <id> --format json → the full traceback key is present in the data.error_details field
  • agents plan errors <id> --format jsonErrorRecoveryService.format_error_output() formats it as JSON via history.to_cli_dict()

Furthermore, ErrorRecoveryService.record_error() also stores a stack_summary (the last 3 frames of traceback.format_exception) in the ErrorRecord model, which itself is accessible via agents plan errors. While the summary is truncated to 3 frames, the full traceback in error_details is not truncated at all.

The wrap_unexpected() utility in core/error_handling.py correctly stores only the last 3 lines of the traceback (via tb_lines[-3:]) and applies redact_value() before storing. The plan executor bypasses this entire safe path.

Evidence

# src/cleveragents/application/services/plan_executor.py, lines 746-754
except Exception as exc:
    # Record via error recovery service if available
    ...
    error_msg = f"{type(exc).__name__}: {exc}"
    plan = self._lifecycle.get_plan(plan_id)
    plan.error_details = {
        "exception_type": type(exc).__name__,
        "traceback": traceback.format_exc(),  # ← FULL TRACE, no redaction
    }
    self._lifecycle._commit_plan(plan)
    self._lifecycle.fail_strategize(plan_id, error_msg)
    raise

Contrast with wrap_unexpected() in core/error_handling.py (lines 301-303):

tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
tb_text = "".join(tb_lines[-3:])  # last 3 lines only
tb_text = redact_value(tb_text)   # redacted

Expected Behavior

Per the spec's secret redaction model (ADR-005), internal stack details must never reach user-facing output. plan.error_details stored in the database should contain at most a truncated, redacted snippet — consistent with how wrap_unexpected() handles it — not a full traceback.format_exc() output.

Actual Behavior

A full Python traceback (all frames, local variables visible in exception chaining, absolute file paths) is committed to the database and returned verbatim by agents plan status --format json and agents plan errors --format json.

Suggested Fix

Replace traceback.format_exc() in all three exception handlers with the same pattern used by wrap_unexpected():

from cleveragents.core.error_handling import wrap_unexpected

tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
tb_snippet = redact_value("".join(tb_lines[-3:]))

plan.error_details = {
    "exception_type": type(exc).__name__,
    "traceback_snippet": tb_snippet,   # truncated + redacted
}

Category

error-handling / security

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [error-handling] — Full traceback stored in plan `error_details`, exposed through public API ### Severity Assessment - **Impact**: Internal stack frames, file paths, and potentially sensitive variable names are written into `plan.error_details["traceback"]` and stored in the database. Any caller reading `agents plan status <id> --format json` or `agents plan errors <id> --format json` receives a verbatim Python traceback. - **Likelihood**: Triggers on every Strategize or Execute phase failure — i.e. the normal error path. - **Priority**: High ### Location - **File**: `src/cleveragents/application/services/plan_executor.py` - **Function**: `PlanExecutor.run_strategize`, `PlanExecutor._run_execute_with_runtime`, `PlanExecutor._run_execute_with_stub` - **Lines**: ~748–752, ~914–919, ~1034–1038 ### Description In all three execution paths (`run_strategize`, `_run_execute_with_runtime`, `_run_execute_with_stub`), when an exception is caught the **full Python traceback** is written directly into `plan.error_details["traceback"]` and then committed to the database via `_commit_plan`: ```python # run_strategize (lines 748-752) plan.error_details = { "exception_type": type(exc).__name__, "traceback": traceback.format_exc(), # ← full stack trace } self._lifecycle._commit_plan(plan) # _run_execute_with_runtime (lines 914-919) plan.error_details = { "exception_type": type(exc).__name__, "traceback": traceback.format_exc(), # ← full stack trace "mode": "runtime", } self._lifecycle._commit_plan(plan) # _run_execute_with_stub (lines 1034-1038) plan.error_details = { "exception_type": type(exc).__name__, "traceback": traceback.format_exc(), # ← full stack trace "mode": "stub", } self._lifecycle._commit_plan(plan) ``` This `error_details` dict is later serialized and returned in: - `agents plan status <id> --format json` → the full `traceback` key is present in the `data.error_details` field - `agents plan errors <id> --format json` → `ErrorRecoveryService.format_error_output()` formats it as JSON via `history.to_cli_dict()` Furthermore, `ErrorRecoveryService.record_error()` also stores a `stack_summary` (the last 3 frames of `traceback.format_exception`) in the `ErrorRecord` model, which itself is accessible via `agents plan errors`. While the summary is truncated to 3 frames, the full traceback in `error_details` is not truncated at all. The `wrap_unexpected()` utility in `core/error_handling.py` correctly stores only the **last 3 lines** of the traceback (via `tb_lines[-3:]`) and applies `redact_value()` before storing. The plan executor bypasses this entire safe path. ### Evidence ```python # src/cleveragents/application/services/plan_executor.py, lines 746-754 except Exception as exc: # Record via error recovery service if available ... error_msg = f"{type(exc).__name__}: {exc}" plan = self._lifecycle.get_plan(plan_id) plan.error_details = { "exception_type": type(exc).__name__, "traceback": traceback.format_exc(), # ← FULL TRACE, no redaction } self._lifecycle._commit_plan(plan) self._lifecycle.fail_strategize(plan_id, error_msg) raise ``` Contrast with `wrap_unexpected()` in `core/error_handling.py` (lines 301-303): ```python tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__) tb_text = "".join(tb_lines[-3:]) # last 3 lines only tb_text = redact_value(tb_text) # redacted ``` ### Expected Behavior Per the spec's secret redaction model (ADR-005), internal stack details must never reach user-facing output. `plan.error_details` stored in the database should contain at most a truncated, redacted snippet — consistent with how `wrap_unexpected()` handles it — not a full `traceback.format_exc()` output. ### Actual Behavior A full Python traceback (all frames, local variables visible in exception chaining, absolute file paths) is committed to the database and returned verbatim by `agents plan status --format json` and `agents plan errors --format json`. ### Suggested Fix Replace `traceback.format_exc()` in all three exception handlers with the same pattern used by `wrap_unexpected()`: ```python from cleveragents.core.error_handling import wrap_unexpected tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__) tb_snippet = redact_value("".join(tb_lines[-3:])) plan.error_details = { "exception_type": type(exc).__name__, "traceback_snippet": tb_snippet, # truncated + redacted } ``` ### Category error-handling / security ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 22:47:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#6637
No description provided.