Skip to content
Back to skills

518 2026 01 28 00 25 00 Api Error Logging Gaps 06bc84f9

ASecurity

**Date:** 2026-01-28 **Owner:** claude-opus **Phase:** Research

  • 9 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 1, 2026
developmentpythongoapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned June 1, 2026

npx -y skills add tools-only/X-Skills --skill 518-2026-01-28_00-25-00_api-error-logging-gaps_06bc84f9 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 518 2026 01 28 00 25 00 Api Error Logging Gaps 06bc84f9?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 518 2026 01 28 00 25 00 Api Error Logging Gaps 06bc84f9
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-518-2026-01-28-00-25-00-api-error-logging-gaps-06b/badge)](https://www.skillsdirectory.com/skills/tools-only-518-2026-01-28-00-25-00-api-error-logging-gaps-06b)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Research – API Error Logging Gaps

**Date:** 2026-01-28
**Owner:** claude-opus
**Phase:** Research

## Goal

Understand why the current debug logs for API/connection errors are insufficient for diagnosing failures, and identify what context is available but not being captured.

## Trigger

User experienced a `ModelAPIError: Connection error` at `2026-01-28T00:23:39.729952+00:00` during a streaming session. The debug logs only showed:

```
[DEBUG  ] [LIFECYCLE] Stream failed: ModelAPIError: Connection error.
```

This provides no actionable information about:
- Which API endpoint failed
- What HTTP status code (if any)
- What model/provider was being used
- Which retry attempt failed
- Request ID for correlation

## Findings

### The Core Problem

**No `ModelAPIError` class exists in tunacode.** The exception comes from pydantic-ai's internal handling. When it reaches tunacode, it's caught and logged with only:

```python
# src/tunacode/core/agents/agent_components/streaming.py:381-384
except Exception as e:
    logger.lifecycle(f"Stream failed: {type(e).__name__}: {e}")
    raise
```

### What IS Captured

| Information | Available | Logged on Error |
|------------|-----------|-----------------|
| Exception type | ✅ | ✅ |
| Exception message | ✅ | ✅ |

### What is NOT Captured (But Available)

| Information | Available At | Not Logged |
|------------|--------------|------------|
| HTTP status code | `HTTPStatusError.response.status_code` | ❌ |
| Request URL | `HTTPStatusError.request.url` | ❌ |
| Model name | `self.model` in main.py:153 | ❌ |
| Provider name | Parsed from model string | ❌ |
| Request ID | `ctx.request_id` in main.py:243 | ❌ |
| Iteration number | `iteration_index` in streaming.py:128 | ❌ |
| Retry attempt | Tenacity internal state | ❌ |
| Response headers | `HTTPStatusError.response.headers` | ❌ |
| Timeout threshold | `timeout` parameter | ❌ |
| Actual duration | Available via timing | ❌ |
| Base URL | Provider config | ❌ |

### Key Files & Line Numbers

**Exception Definitions:**
- `src/tunacode/exceptions.py:81-314` - Custom exceptions (no ModelAPIError)
- `GlobalRequestTimeoutError`: lines 268-278

**Stream Error Handler:**
- `src/tunacode/core/agents/agent_components/streaming.py:381-384` - Minimal logging

**Request Context (has request_id but unused on error):**
- `src/tunacode/core/agents/main.py:172-181` - Creates request_id
- `src/tunacode/core/agents/main.py:243-244` - Logs request_id only on SUCCESS

**HTTP Retry Config:**
- `src/tunacode/core/agents/agent_components/agent_config.py:385-393` - Tenacity transport

**Logging Manager:**
- `src/tunacode/core/logging/manager.py:119-124` - `lifecycle()` only works in debug_mode

### Critical Architecture Gaps

1. **lifecycle() is debug-only**: Stream failures use `logger.lifecycle()` which is gated by `debug_mode`. Production errors get NO log entry.

2. **No HTTPStatusError enrichment**: The retry transport catches `HTTPStatusError` for retries, but when retries are exhausted, the exception bubbles up with no context extraction.

3. **Request ID only logged on success**: `main.py:243-244` logs the request ID, but only in the success path. Error paths never log it.

4. **Silent exception handlers**: `streaming.py` has 8+ `except Exception: pass` blocks for instrumentation that could capture diagnostic info but discard it instead.

5. **No exception wrapping for API errors**: Tool errors get wrapped in `ToolExecutionError` with context. API errors bubble as raw pydantic-ai/httpx exceptions.

## Key Patterns / Solutions Found

### Pattern: Error Context at Each Layer

The codebase follows "fail fast, fail loud" but doesn't enrich errors as they propagate:

```
HTTP Layer (httpx) → pydantic-ai → streaming.py → main.py → UI
     ↑                                    ↑
     Has full context              Logs only type+message
```

### Pattern: Attribute-Based Error Rendering

`ui/renderers/errors.py` extracts context via `getattr()`:

```python
EXCEPTION_CONTEXT_ATTRS = {
    "tool_name": "Tool",
    "path": "Path",
    "model": "Model",  # ← exists but never set on API errors
    ...
}
```

If API errors carried `model`, `status_code`, `request_id` attributes, the renderer would display them automatically.

## Recommended Fix

### Immediate: Enrich Exception Before Logging

At `streaming.py:381`:

```python
except Exception as e:
    # Extract context before logging
    context = {
        "request_id": request_id,
        "iteration": iteration_index,
        "model": model_name,
    }
    if hasattr(e, 'response'):
        context["status_code"] = e.response.status_code
        context["url"] = str(e.request.url)

    logger.error(f"Stream failed: {type(e).__name__}: {e}", extra=context)
    raise
```

### Medium-term: Create ServiceAPIError

Add to `exceptions.py`:

```python
class ServiceAPIError(ServiceError):
    def __init__(
        self,
        message: str,
        *,
        provider: str | None = None,
        model: str | None = None,
        status_code: int | None = None,
        request_id: str | None = None,
        suggested_fix: str | None = None,
    ):
        self.provider = provider
        self.model = model
        self.status_code = status_code
        self.request_id = request_id
        ...
```

Then wrap at the boundary where pydantic-ai errors surface.

## Knowledge Gaps

- What does pydantic-ai's `ModelAPIError` actually contain? Need to inspect the exception object.
- Does tenacity expose retry attempt number in a callback hook?
- Should error logging be unconditional (not gated by debug_mode)?

## References

- `src/tunacode/core/agents/agent_components/streaming.py`
- `src/tunacode/core/agents/main.py`
- `src/tunacode/core/agents/agent_components/agent_config.py`
- `src/tunacode/exceptions.py`
- `src/tunacode/ui/renderers/errors.py`
- `src/tunacode/core/logging/manager.py`

Files in this skill

  • README.md993 B
  • skill.md5.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.