Automated URL fetch failure handling with strategy exhaustion, memory learning, and human-in-the-loop recovery. Use when fetches fail and you need intelligent retry, pattern learning, and human collaboration.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add grahama1970/agent-skills --skill debug-fetcher --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Debug Fetcher?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/grahama1970-debug-fetcher)More formats (shields.io, HTML) on the badges page.
---
name: debug-fetcher
description: >
Automated URL fetch failure handling with strategy exhaustion, memory learning,
and human-in-the-loop recovery. Use when fetches fail and you need intelligent
retry, pattern learning, and human collaboration.
allowed-tools: Bash, Read, Write, Task
triggers:
- debug fetch
- debug fetcher
- why did fetch fail
- fetch failure
- analyze fetch failure
- retry fetch
- resilient fetch
- extraction failure
- empty extracted text
- result.text contains HTML
- content extraction bug
metadata:
short-description: Failure-to-recovery automation for URL fetching
provides:
- debug-fetcher
composes:
- task-monitor
- agentic-evals
disciplines:
- research-retrieval
- developer-tooling
---
# Debug-Fetcher Skill
Automated fetch failure handling that:
1. **Queries /memory first** - applies learned strategies before trying defaults
2. **Exhausts all strategies** - direct, playwright, wayback, brave, jina, proxy, UA rotation
3. **Stores successes** - saves working strategies to /memory for future runs
4. **Collaborates with humans** - uses /interview when all automated strategies fail
## Quick Start
```bash
# Fetch single URL with failure handling
./run.sh fetch https://example.com
# Fetch batch with failure handling
./run.sh fetch-batch urls.txt
# Check what was learned about a domain
./run.sh recall example.com
# Export all learned strategies
./run.sh export-learnings
```
## How It Works
```
URL Request
│
▼
┌──────────────────────────┐
│ 1. Query /memory │
│ "What works for this │
│ domain?" │
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ 2. Try learned strategy │
│ (if exists) │
└──────────────────────────┘
│
▼ (fail or no learned strategy)
┌──────────────────────────┐
│ 3. Exhaust strategies: │
│ - direct fetch │
│ - playwright │
│ - wayback machine │
│ - brave alternates │
│ - jina reader │
│ - proxy rotation │
│ - user-agent rotation │
└──────────────────────────┘
│
▼ (all fail)
┌──────────────────────────┐
│ 4. Launch /interview │
│ Ask human for help: │
│ - Credentials? │
│ - Mirror URL? │
│ - Manual download? │
│ - Skip this URL? │
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ 5. Store to /memory │
│ - Successful strategy │
│ - Domain patterns │
│ - Human-provided info │
└──────────────────────────┘
```
## Memory Schema
Each learned strategy stores:
| Field | Description |
|-------|-------------|
| `domain` | Target domain (e.g., "nytimes.com") |
| `path_pattern` | URL path pattern (e.g., "/article/*") |
| `successful_strategy` | What worked (e.g., "playwright") |
| `headers` | Custom headers that helped |
| `timing_ms` | How long the fetch took |
| `success_rate` | Historical success rate |
| `failure_count` | How many times this domain failed |
| `last_used` | Timestamp of last use |
| `discovered_at` | When strategy was first learned |
## Commands
| Command | Description |
|---------|-------------|
| `fetch <url>` | Fetch single URL with failure handling |
| `fetch-batch <manifest>` | Fetch list of URLs with failure handling |
| `recall <domain>` | Show learned strategies for domain |
| `export-learnings` | Export all strategies to JSON |
### fetch-batch Options
| Option | Default | Description |
|--------|---------|-------------|
| `--concurrency` | 4 | Max concurrent fetches |
| `--no-memory` | false | Disable memory integration |
| `--output` | - | Output JSON file |
| `--json-stream` | false | Output NDJSON per URL (streaming progress) |
| `--task-monitor/--no-task-monitor` | true | Enable/disable task-monitor integration |
## Task-Monitor Integration
debug-fetcher integrates with the centralized task-monitor for live progress tracking:
```bash
# Run batch fetch with task-monitor (enabled by default)
./run.sh fetch-batch urls.txt
# View progress in task-monitor TUI
cd ~/.pi/skills/task-monitor
uv run python monitor.py tui --filter debug-fetcher
# Or check state file directly
cat /path/to/debug-fetcher/debug_fetcher_task_state.json | jq
```
State file schema:
```json
{
"completed": 50,
"total": 100,
"progress_pct": 50.0,
"strategies": {
"direct": {"attempts": 30, "successes": 25, "rate": 0.833},
"playwright": {"attempts": 20, "successes": 18, "rate": 0.9}
},
"stats": {
"success_count": 45,
"failure_count": 5,
"success_rate": 0.9,
"learned_count": 12
},
"failures": [...],
"status": "running"
}
```
## NDJSON Streaming Output
For long-running batch jobs, use `--json-stream` to output one JSON object per line:
```bash
./run.sh fetch-batch urls.txt --json-stream | tee results.jsonl
# Each line:
# {"url": "https://...", "success": true, "winning_strategy": "playwright", "attempts": 2, "timing_ms": 1234}
```
This enables:
- Real-time progress monitoring via `tail -f results.jsonl | jq`
- Integration with streaming parsers
- Resume from partial runs
## Environment Variables
| Variable | Description |
|----------|-------------|
| `DEBUG_FETCHER_MEMORY_SCOPE` | Memory scope for storing strategies (default: "fetcher_strategies") |
| `DEBUG_FETCHER_MAX_RETRIES` | Max retries per strategy (default: 2) |
| `DEBUG_FETCHER_INTERVIEW_THRESHOLD` | Min failures before triggering interview (default: 3) |
## Integration with Fetcher
Debug-fetcher wraps the standard fetcher skill and adds failure handling capabilities.
All fetcher environment variables (BRAVE_API_KEY, FETCHER_EMIT_MARKDOWN, etc.) are respected.
## Examples
### Learning from Failures
After fetching a batch of URLs, debug-fetcher stores successful strategies:
```bash
# Fetch a batch
./run.sh fetch-batch urls.txt --output results.jsonl
# View what was learned
./run.sh recall attack.mitre.org
# Output:
# Domain: attack.mitre.org
# Strategy: playwright
# Success rate: 95%
# Last used: 2025-01-30
# Next time, playwright will be tried first for attack.mitre.org
./run.sh fetch https://attack.mitre.org/techniques/T1059
```
### Human-in-the-Loop Interview
When all strategies fail, an interview is generated:
```bash
# Fetch batch with failures
./run.sh fetch-batch difficult_urls.txt
# Interview generated at: /tmp/interview_abc123.json
# Run: ./agents/skills/interview/run.sh /tmp/interview_abc123.json
# Example interview questions:
# - "Failed 5 URLs from nytimes.com. Do you have credentials?"
# - "archive.org not working. Try a mirror URL?"
```
### YouTube URL Handling
YouTube URLs are automatically detected and handled via the `/ingest-youtube` skill:
```bash
# YouTube URLs use transcript extraction
./run.sh fetch https://www.youtube.com/watch?v=abc123
# Uses: /ingest-youtube skill for transcript extraction
# Falls back to other strategies if transcript unavailable
```
### Batch Analysis
After a batch run, analyze patterns:
```python
from debug_fetcher.batch_analyzer import analyze_batch, get_failure_summary
# Get summary
summary = get_failure_summary(results)
# {
# "total": 1000,
# "success": 850,
# "failed": 150,
# "success_rate": "85.0%",
# "top_failing_domains": [
# {"domain": "nytimes.com", "count": 45},
# {"domain": "wsj.com", "count": 30}
# ],
# "patterns": [
# "All 45 URLs from nytimes.com returned HTTP 403",
# "High failure rate: 50% of failures are paywalled sites"
# ]
# }
```
## Fetch Failure vs Extraction Failure
**Critical distinction**: "Empty content" can mean two different things:
| Symptom | Fetch Failure | Extraction Failure |
|---------|---------------|-------------------|
| HTTP status | Non-200 | 200 |
| `downloads/` content | Empty or error page | Full HTML present |
| `extracted_text/` | Empty | Empty (BUG) or HTML (BUG) |
| Root cause | Network/auth/JS rendering | `evaluate_result_content()` bug |
| Fix | Add to SPA_FALLBACK_DOMAINS, proxy, etc. | Fix extraction code |
### Diagnosing the Difference
```bash
# Step 1: Check HTTP status
jq '.items[0].status' consumer_summary.json
# 200 = fetch succeeded, issue is extraction
# 403/401/etc = fetch failed, try other strategies
# Step 2: Check raw download
head -c 500 downloads/*.html
# If contains real content → extraction bug
# If contains JS shell only → needs Playwright
# If contains error page → fetch failed
# Step 3: Check extracted text
head -c 500 extracted_text/*.txt
# If contains "<!DOCTYPE html>" → extraction returned HTML (BUG)
# If contains clean text → working correctly
# If empty → extraction failed
# Step 4: Verify method used
jq '.items[0].method' consumer_summary.json
# "aiohttp" = direct fetch
# "playwright" = JS rendering used
```
### Real-World Example (Fixed 2026-02-04)
**Bug**: `result.text` contained raw HTML instead of extracted text.
**Evidence**:
```
downloads/abc.html → <!DOCTYPE html>...(full HTML)
extracted_text/abc.txt → <!DOCTYPE html>...(same HTML - BUG!)
```
**Fix**: `evaluate_result_content()` now replaces `result.text` with `assessment.text` (trafilatura output).
**Lesson**: Always compare `downloads/` vs `extracted_text/` - they should be different formats.
## Recovery Actions
When human provides help via interview:
| Action Type | Description | Example |
|-------------|-------------|---------|
| `credentials` | Login credentials provided | username/password for site |
| `mirror` | Alternative URL to try | archive.org mirror |
| `manual_file` | Human downloaded file manually | Path to local PDF |
| `skip` | URL not needed | "Not critical" |
| `retry` | Try again later | Server was down |
| `custom_strategy` | Specific approach suggested | "Use proxy" |
## Files
```
.pi/skills/debug-fetcher/
├── SKILL.md # This file
├── run.sh # Entry point
├── pyproject.toml # Dependencies
└── debug_fetcher/ # Python package
├── __init__.py
├── cli.py # CLI commands
├── memory_schema.py # FetchStrategy dataclass
├── memory_bridge.py # Recall/learn from /memory
├── strategy_engine.py # Strategy exhaustion loop
├── batch_analyzer.py # Analyze batch failures
├── interview_generator.py # Generate /interview JSON
├── interview_processor.py # Process interview responses
├── recovery_executor.py # Execute recovery actions
└── pdf_bridge.py # Cross-skill integration with debug-pdf
```
## Companion Skill: debug-pdf
`debug-fetcher` and `debug-pdf` work together in the pipeline:
```
URL → debug-fetcher → /fetcher → /extractor → debug-pdf
↓ ↓
fetch fail extraction fail
↓ ↓
retry/recover analyze PDF issues
↓ ↓
/memory /memory
```
**Shared failure patterns:**
| Pattern | debug-fetcher | debug-pdf |
|---------|---------------|-----------|
| `auth_required` | HTTP 401/403 | N/A |
| `access_restricted` | HTTP 403 | N/A |
| `paywall_detected` | Soft paywall | N/A |
| `password_protected` | N/A | Encrypted PDF |
| `scanned_no_ocr` | N/A | No text layer |
| `archive_org_wrap` | Wayback wrapper | Wayback wrapper |
**Cross-skill notifications:**
- When debug-fetcher successfully fetches a PDF but detects issues (password protected, scanned), it notifies debug-pdf via agent-inbox
- When debug-fetcher fails to fetch a PDF URL, it notifies debug-pdf for tracking
## Related Skills
- `/memory` - Stores learned fetch strategies
- `/interview` - Human collaboration for unrecoverable URLs
- `/ingest-youtube` - YouTube transcript extraction
- `/fetcher` - Core URL fetching functionality
- `/extractor` - Content extraction from fetched documents
- `/debug-pdf` - Companion skill for PDF extraction failures
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!