Files
Aperant/apps/backend/task_logger
StillKnotKnown 988ec0c25b feat(task-logger): strip ANSI escape codes from logs and extend coverage (#1411)
* auto-claude: subtask-1-1 - Add strip_ansi_codes() utility function to task_lo

* auto-claude: subtask-1-2 - Apply ANSI sanitization to TaskLogger.log_with_detail() and TaskLogger.tool_end()

Changes:
- Import strip_ansi_codes from utils in logger.py
- Apply strip_ansi_codes to detail parameter in log_with_detail()
- Apply strip_ansi_codes to stored_detail in tool_end()
- Fix circular import in utils.py using TYPE_CHECKING

Co-Authored-By: Claude <noreply@anthropic.com>

* auto-claude: subtask-1-3 - Apply ANSI sanitization to StreamingLogCapture.pro

- Import strip_ansi_codes from utils module
- Apply sanitization to process_text() method before logging
- Ensures ANSI escape codes are removed from streaming text output

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(task-logger): strip ANSI escape codes from logs and extend coverage

Backend (Python):
- Extend CSI pattern to support private mode parameters (?<>=)
- Add None handling to strip_ansi_codes() function
- Add sanitization to TaskLogger.log() method (critical gap fix)
- Export strip_ansi_codes in task_logger public API
- Add 25 comprehensive unit tests for strip_ansi_codes()

Frontend (TypeScript):
- Extend CSI pattern to support private mode parameters (?<>=)
- Apply ANSI sanitization to merge preview error messages in Kanban

This ensures clean display of task logs and error messages in the UI
by removing terminal color/formatting escape sequences.

* fix(task_logger): resolve cyclic import issue

Move strip_ansi_codes import from module-level to local imports in
logger.py to avoid cyclic import when __init__.py imports both modules.
Also use lazy import in get_task_logger() to avoid cyclic import at module level.

Fixes NameError in test_planner_session_does_not_trigger_post_session_processing_on_retry

* fix(tests): add missing os import in test_task_logger.py

Signed-off-by: StillKnotKnown <stillknotknown@users.noreply.github.com>

* fix(task_logger): sanitize content parameter in log_with_detail()

- Apply ANSI stripping to both content and detail in log_with_detail()
- Remove unused Path import from test file
- Add test for content sanitization in log_with_detail()

Fixes issue identified by CodeRabbit review where log_with_detail()
only sanitized detail but not content, allowing ANSI codes to leak
into stored logs and UI components.

Signed-off-by: StillKnotKnown <stillknotknown@users.noreply.github.com>

* fix(task_logger): sanitize result and content in tool_end()

- Apply ANSI stripping to display_result and content in tool_end()
- Add test for result and content sanitization in tool_end()

Fixes CodeRabbit review comment where tool_end() only sanitized
detail but not result/content, allowing ANSI codes to leak into
the stored log content and UI components.

Signed-off-by: StillKnotKnown <stillknotknown@users.noreply.github.com>

* refactor: consolidate duplicate inline imports and remove redundant assert

- Consolidate 3 inline imports of strip_ansi_codes in tool_end() to single import
- Consolidate 2 inline imports in log_with_detail() to single import
- Remove redundant 'assert True' in test_public_api_exports

Addressed CodeRabbit review comments about code quality.

Signed-off-by: StillKnotKnown <stillknotknown@users.noreply.github.com>

* fix: scope strip_ansi_codes usage within import block

Move content and detail sanitization inside the import block to
resolve CodeQL false positive about potentially uninitialized
local variable. The import and all usages are now within
the same conditional scope.

Signed-off-by: StillKnotKnown <stillknotknown@users.noreply.github.com>

* fix: address CodeRabbit review comments

- Add lazy import of TaskLogger in update_task_logger_path() to avoid
  potential NameError when accessing TaskLogger.LOG_FILE
- Sanitize text before checking for empty in capture.process_text() to
  avoid logging blank entries when input contains only ANSI codes

These were false positives in practice but improve code clarity
and static analysis results.

Signed-off-by: StillKnotKnown <stillknotknown@users.noreply.github.com>

* fix: sanitize message, tool_input, and subphase parameters in logging methods

Apply strip_ansi_codes() to:
- start_phase() message parameter
- end_phase() message parameter
- tool_start() tool_input parameter
- start_subphase() subphase parameter

This ensures consistency with other logging methods (log, log_with_detail,
tool_end) which all sanitize input before storage.

Addresses Auto Claude PR Review findings:
- 🟡 [31465b234445] start_phase() message not sanitized
- 🟡 [18192bb9d19d] end_phase() message not sanitized
- 🟡 [16aa996a0d8d] tool_start() tool_input not sanitized
- 🟡 [b39df9833b80] start_subphase() subphase not sanitized

* refactor: extract ANSI utilities to separate module to fix cyclic import

Create new task_logger/ansi.py module containing strip_ansi_codes() and
related ANSI patterns, removing the dependency on logger.py.

This resolves CodeQL cyclic import warnings:
- utils.py imports TaskLogger (TYPE_CHECKING only)
- logger.py imports strip_ansi_codes from utils
- New ansi.py has no dependencies on other task_logger modules

Changes:
- Create apps/backend/task_logger/ansi.py with strip_ansi_codes()
- Update __init__.py to import strip_ansi_codes directly from .ansi
- Update logger.py to import from .ansi instead of .utils (7 locations)
- Update capture.py to import from .ansi instead of .utils
- Update tests to import from task_logger.ansi directly
- Remove duplicate ANSI code patterns from utils.py

All 27 tests pass.

* fix: address CodeRabbit review comments

1. Expand ANSI_CSI_PATTERN to match full ANSI/VT100 CSI final-byte range
   - Old pattern: \x1b\[[?><=0-9;]*[A-Za-z]
   - New pattern: \x1b\[[0-?]*[ -/]*[@-~]
   - Now strips bracketed paste sequences (\x1b[200~, \x1b[201~) and other
     CSI sequences with non-letter final bytes

2. Print sanitized phase_message instead of raw message in start_phase/end_phase
   - Prevents ANSI codes from appearing in console output
   - Keeps console output consistent with stored log content

3. Add test for bracketed paste sequences (test_csi_bracketed_paste)

All 28 tests pass.

* fix: address CodeRabbit review comments

1. Use platform abstraction in subprocess-spawn test
   - Replace process.platform === 'win32' with isWindows() from platform module
   - Follows coding guidelines for cross-platform checks

2. Add trailing newline to test_task_logger.py for POSIX compliance

All tests pass (28 Python tests, 14 TypeScript tests).

* fix: resolve all Auto Claude PR Review findings

Backend:
- Move strip_ansi_codes to module-level import in logger.py
- Removes 7 duplicate inline imports, keeping only 1 at top of file

Frontend:
- Update ANSI_CSI_PATTERN to match backend regex: /\x1b\[[0-?]*[ -/]*[@-~]/g
- Change final byte pattern from [A-Za-z] to [@-~] for full CSI coverage
- Add [ -/]* for intermediate bytes handling
- Add tests for bracketed paste sequences and private mode parameters

This resolves 4 remaining findings:
- 🔵 [LOW] Duplicate inline imports - FIXED
- 🟡 [MEDIUM] Frontend regex missing CSI final bytes - FIXED
- 🔵 [LOW] Frontend regex missing intermediate bytes - FIXED
- 🔵 [LOW] Frontend missing bracketed paste tests - FIXED

All tests pass: 28 Python + 36 TypeScript = 64 total.

* fix: sanitize before truncation to avoid partial ANSI remnants

Truncating before sanitizing can cut ANSI escape sequences in half,
leaving stray control characters that the regex won't remove.

Changes:
- Sanitize display_result before truncation (300 char limit)
- Sanitize stored_detail before truncation (10KB limit)
- Use original detail length for truncation message

Fixes CodeRabbit review comment about tool_end() sanitization order.

All 28 tests pass.

* refactor: address CodeRabbit nitpick comments

Backend:
- Remove redundant outer guard "if content or detail:" in log_with_detail()
- Remove redundant sanitization of content in tool_end() (already sanitized)
- Reduces nesting and removes unnecessary conditional checks

Frontend:
- Standardize all subprocess test timeouts to 30000ms for Windows CI
- Provides consistent margin for slower Windows CI environment

All tests pass: 28 Python + 14 TypeScript = 42 total.

* refactor: remove redundant conditions in start_phase and end_phase

Since phase_message always has a fallback default value, it can never be
falsy. The if phase_message: guards are unnecessary.

Simplifies code by:
- Removing redundant condition before sanitization
- Removing redundant condition before print

All 28 tests pass.

* fix: address Auto Claude review LOW severity findings

1. Fix truncation message to report sanitized length
   - Use sanitized_len for truncation message instead of unsanitized detail length
   - Ensures reported length matches visible content

2. Fix misleading comment in utils.py
   - Update comment to reflect that ANSI functions are in ansi.py module
   - Clarifies architectural decision rather than implying an import

All 28 tests pass.

* fix: apply ANSI sanitization to all subprocess error paths

Apply stripAnsiCodes() to merge and create PR error output
for consistency with the preview handler.

* refactor: remove unused variable original_len (dead code)

Remove the unused original_len variable that was leftover
from the previous ANSI sanitization fix.

---------

Signed-off-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
2026-01-26 12:47:56 +01:00
..
2026-01-02 11:56:36 +01:00
2026-01-02 11:56:36 +01:00
2026-01-02 11:56:36 +01:00
2026-01-02 11:56:36 +01:00

Task Logger Package

A modular, well-organized logging system for Auto Claude tasks with persistent storage and real-time UI updates.

Package Structure

task_logger/
├── __init__.py          # Package exports and public API
├── models.py            # Data models (LogPhase, LogEntryType, LogEntry, PhaseLog)
├── logger.py            # Main TaskLogger class
├── storage.py           # Log persistence and file I/O
├── streaming.py         # Streaming marker emission for UI updates
├── utils.py             # Utility functions (get_task_logger, etc.)
├── capture.py           # StreamingLogCapture for agent sessions
└── README.md            # This file

Modules

models.py

Contains the core data models:

  • LogPhase: Enum for execution phases (PLANNING, CODING, VALIDATION)
  • LogEntryType: Enum for log entry types (TEXT, TOOL_START, TOOL_END, etc.)
  • LogEntry: Dataclass representing a single log entry
  • PhaseLog: Dataclass representing logs for a single phase

logger.py

Main logging implementation:

  • TaskLogger: Primary class for task logging with phase management, tool tracking, and event logging

storage.py

Persistent storage functionality:

  • LogStorage: Handles JSON file storage and retrieval
  • load_task_logs(): Load logs from a spec directory
  • get_active_phase(): Get currently active phase

streaming.py

Real-time UI updates:

  • emit_marker(): Emit streaming markers to stdout for UI consumption

utils.py

Convenience utilities:

  • get_task_logger(): Get or create global logger instance
  • clear_task_logger(): Clear global logger
  • update_task_logger_path(): Update logger path after directory rename

capture.py

Agent session integration:

  • StreamingLogCapture: Context manager for capturing agent output and logging it

Usage

Basic Usage

from task_logger import TaskLogger, LogPhase

# Create logger for a spec
logger = TaskLogger(spec_dir)

# Start a phase
logger.start_phase(LogPhase.CODING, "Beginning implementation")

# Log messages
logger.log("Implementing feature X...")
logger.log_info("Processing file: app.py")
logger.log_success("Feature X completed!")
logger.log_error("Failed to process file")

# Track tool usage
logger.tool_start("Read", "/path/to/file.py")
logger.tool_end("Read", success=True, result="File read successfully")

# End phase
logger.end_phase(LogPhase.CODING, success=True)

Using Global Logger

from task_logger import get_task_logger

# Get/create global logger
logger = get_task_logger(spec_dir)
logger.log("Using global logger instance")

Capturing Agent Output

from task_logger import StreamingLogCapture, LogPhase

with StreamingLogCapture(logger, LogPhase.CODING) as capture:
    async for msg in client.receive_response():
        capture.process_message(msg)

Loading Logs

from task_logger import load_task_logs, get_active_phase

# Load all logs
logs = load_task_logs(spec_dir)

# Get active phase
active = get_active_phase(spec_dir)

Design Principles

Separation of Concerns

  • Models: Pure data structures with no business logic
  • Storage: File I/O and persistence isolated from logging logic
  • Logger: Business logic for logging operations
  • Streaming: UI update mechanism separated from core logging
  • Utils: Helper functions for common patterns
  • Capture: Agent integration separated from core logger

Backwards Compatibility

The refactored package maintains 100% backwards compatibility. All existing imports continue to work:

# These imports still work (re-exported from task_logger.py)
from task_logger import LogPhase, TaskLogger, get_task_logger

Type Hints

All functions and classes include comprehensive type hints for better IDE support and code clarity.

Testability

Each module has a single responsibility, making it easier to test individual components.

Migration Guide

No migration needed! The refactoring maintains full backwards compatibility.

Existing code continues to work without changes:

from task_logger import LogPhase, TaskLogger, get_task_logger

New code can import from specific modules if desired:

from task_logger.models import LogPhase
from task_logger.logger import TaskLogger
from task_logger.utils import get_task_logger

Benefits of Refactoring

  1. Improved Maintainability: 52-line entry point vs. 818-line monolith
  2. Clear Separation: Each module has a single, well-defined purpose
  3. Better Testing: Isolated modules are easier to unit test
  4. Enhanced Readability: Easier to find and understand specific functionality
  5. Scalability: New features can be added to appropriate modules
  6. No Breaking Changes: Full backwards compatibility maintained