Route Researcher

SkillAI & models

Research mountain routes and generate comprehensive route beta reports for North American peaks, aggregating weather forecasts, avalanche conditions, daylight windows, trip reports, and access info from PeakBagger, SummitPost, WTA, AllTrails, and regional avalanche centers. Use when planning a climb, hike, or scramble, or when asked for route beta, trail conditions, peak research, or mountaineering trip planning.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Route Researcher skill

What this skill tells your AI

The instructions your AI receives, as published by dreamiurg/claude-mountaineering-skills in skills/route-researcher/SKILL.md and read by ahel’s review.

Research mountain peaks across North America and generate comprehensive route beta reports combining data from multiple sources including PeakBagger, SummitPost, WTA, AllTrails, weather forecasts, avalanche conditions, and trip reports.

Data Sources: This skill aggregates information from specialized mountaineering websites (PeakBagger, SummitPost, Washington Trails Association, AllTrails, The Mountaineers, and regional avalanche centers). The quality of the generated report depends on the availability of information on these sources. If your target peak lacks coverage on these websites, the report may contain limited details. The skill works best for well-documented peaks in North America.

When to Use This Skill

Use this skill when the user requests:

  • Research on a specific mountain peak
  • Route beta or climbing information
  • Trip planning information for peaks
  • Current conditions for mountaineering objectives

Examples:

  • "Research Mt Baker"
  • "I'm planning to climb Sahale Peak next month, can you research the route?"
  • "Generate route beta for Forbidden Peak"

Progress Checklist

Research Progress:

  • Phase 1: Peak Identification (peak validated, ID obtained)
  • Phase 2: Peak Information Retrieval (coordinates and details obtained)
  • Phase 3: Data Gathering (parallel execution)
    • Phase 3a: Python conditions fetch (weather, air quality, daylight, avalanche, peakbagger stats/ascents)
    • Phase 3b: Researcher agents (3 in parallel - web sources + trip reports)
    • Phase 3c: Results aggregated
    • Phase 3d: Access/permits (inline WebSearch)
  • Phase 4: Route Analysis (synthesize route, crux, hazards)
  • Phase 5: Report Generation (Report Writer agent)
  • Phase 6: Report Review & Validation (Report Reviewer agent)
  • Phase 7: Completion (user notified, next steps provided)

Orchestration Workflow

Phase 1: Peak Identification

Goal: Identify and validate the specific peak to research.

  1. Extract Peak Name from user message

    • Look for peak names, mountain names, or climbing objectives
    • Common patterns: "Mt Baker", "Mount Rainier", "Sahale Peak", etc.
  2. Search PeakBagger using peakbagger-cli:

    uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak search "{peak_name}" --format json
    
    • Parse JSON output to extract peak matches
    • Each result includes: peak_id, name, elevation (feet/meters), location, url
  3. Handle Multiple Matches:

    • If multiple peaks found: Use AskUserQuestion to present options

      • For each option, show: peak name, elevation, location, AND PeakBagger URL
      • Format each option description as: "[Peak Name] ([Elevation], [Location]) - [PeakBagger URL]"
      • This allows user to click through and verify the correct peak
      • Let user select the correct peak
      • Provide "Other" option if none match
    • If single match found: Confirm with user

      • Present confirmation message with peak details and PeakBagger link
      • Show: "Found: [Peak Name] ([Elevation], [Location])"
      • Include PeakBagger URL in the message so user can verify: "[PeakBagger URL]"
      • Use AskUserQuestion: "Is this the correct peak? You can verify at [PeakBagger URL]"
    • If no matches found:

      • Try peak name variations systematically (see "Peak Name Variations" section):
        • Word order reversal: "Mountain Pratt" → "Pratt Mountain"
        • Title variations: Mt/Mount, St/Saint
        • Add location: Include state or range name
        • Remove titles: Try just the core name
      • Run multiple searches in parallel with different variations
      • Combine results and present best matches to user
      • If still no results, use AskUserQuestion to ask for:
        • A different peak name variation
        • Direct PeakBagger peak ID or URL
        • General PeakBagger search
  4. Extract Peak ID:

    • From search results JSON, extract the peak_id field
    • Store for use in subsequent peakbagger-cli commands
    • Also store the PeakBagger URL for reference links

Phase 2: Peak Information Retrieval

Goal: Get detailed peak information and coordinates needed for location-based data gathering.

This phase must complete before Phase 3, as coordinates are required for weather, daylight, and avalanche data.

Retrieve detailed peak information using the peak ID from Phase 1:

uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak show {peak_id} --format json

This returns structured JSON with:

  • Peak name and alternate names
  • Elevation (feet and meters)
  • Prominence (feet and meters)
  • Isolation (miles and kilometers)
  • Coordinates (latitude, longitude in decimal degrees)
  • Location (county, state, country)
  • Routes (if available): trailhead, distance, vertical gain
  • Peak list memberships and rankings
  • Standard route description (if available in routes data)

Error Handling:

  • If peakbagger-cli fails: Fall back to WebSearch/WebFetch and note in "Information Gaps"
  • If specific fields missing in JSON: Mark as "Not available" in gaps section
  • Rate limiting: Built into peakbagger-cli (default 2 second delay)

Once coordinates are obtained from this step, immediately proceed to Phase 3.

Phase 3: Data Gathering

Goal: Gather comprehensive route information from all available sources.

Execution Strategy: Run Python script for deterministic API data + dispatch specialized agents in parallel for web research. This hybrid approach minimizes token usage while maximizing parallelism.

Step 3A: Fetch Conditions Data (Python Script)

Run the conditions fetcher script to gather all API-based data:

cd "{repo_root}/skills/route-researcher/tools"
uv run python fetch_conditions.py \
  --coordinates "{latitude},{longitude}" \
  --elevation {elevation_m} \
  --peak-name "{peak_name}" \
  --peak-id {peak_id} \
  --trailhead "{trailhead_lat},{trailhead_lon}" \
  --distance-mi {round_trip_distance_mi} \
  --gain-ft {total_gain_ft} \
  --start-time "{HH:MM}" \
  --waypoint "{lat1},{lon1}" --waypoint "{lat2},{lon2}"

Optional args: --trailhead enables multi-county path sampling (trailhead→summit); hospital/ranger lookups always run from the summit regardless; --distance-mi/--gain-ft enable time_estimates; --start-time (with distance + gain) enables itinerary; --waypoint (2+) enables bearings.

This returns JSON with:

  • weather: 7-day forecast with temperatures, precipitation, freezing levels; each day includes snow_line_note (human-readable framing of freezing level as snow line) and near_summit (bool: true when freezing level within 2000 ft of summit)
  • air_quality: AQI ratings and any concerns
  • daylight: Full twilight table — astronomical_dawn, nautical_dawn, civil_twilight (dawn), sunrise, sunset, civil_dusk, nautical_dusk, astronomical_dusk; values are null at high latitudes when sun doesn't reach threshold (white nights); daylight_hours, timezone
  • time_estimates: Roped/unroped + 3-tier pacing (roped_hr, unroped_hr, fast_hr, moderate_hr, leisurely_hr) — only present when --distance-mi and --gain-ft CLI args are provided
  • itinerary: Trip schedule with safety signals (start_time, summit_eta, turnaround_by, return_eta, total_hr, after_dark bool, dusk_cutoff, note) — only present when --start-time, --distance-mi, AND --gain-ft are all provided; after_dark: true is a safety warning that must be prominently surfaced; total_hr is the full round-trip duration in hours
  • bearings: Navigation bearings between waypoints (segments[] with bearing_deg, distance_mi, cumulative_distance_mi; total_distance_mi) — only present when 2 or more --waypoint "lat,lon" args are provided
  • avalanche: NWAC region and URL for manual check
  • peakbagger: Ascent statistics and recent ascents (if peak_id provided)
  • counties: Counties traversed trailhead→summit (counties[] with county_name, county_fips, state_name, state_code); sampled bool and sample_points int indicate whether path sampling ran (requires --trailhead); without --trailhead only the summit county is returned
  • nearest_hospital: Nearest hospitals/ERs (hospitals[] with name, lat, lon, distance_miles, emergency, and phone/website/address when OSM has them); sorted emergency-first then by distance; max 3
  • ranger_station: Nearest ranger stations (stations[] with name, lat, lon, distance_miles, and phone/website/address when present) + optional admin_district (district_name, forest_name, region) when the summit coordinates intersect a USFS ranger district
  • campgrounds: Established campgrounds within ~12 mi (20 km) (campgrounds[] with name, lat, lon, distance_miles, camp_type, backcountry, operator, and website when present); backcountry/high camps are NOT included — extract those from trip reports
  • gaps: Any API failures noted for report

Run this in parallel with Step 3B — include both the Bash command for fetch_conditions.py and all 3 Task calls in the same response turn to maximize parallelism.

Step 3B: Dispatch Researcher Agents (Parallel)

Dispatch 3 Researcher agents in a single message (all Task calls together). Each agent researches assigned sources and fetches trip report content directly.

Agent 1: PeakBagger + SummitPost

Task(
  subagent_type="general-purpose",
  model="sonnet",
  prompt="""You are a route researcher gathering mountaineering data for {peak_name}.

## Your Assignment
Research from these sources: PeakBagger, SummitPost

**Discover first (web sources):** for SummitPost, run a `site:summitpost.org` WebSearch to get exact URLs, then fetch those (don't WebFetch guessed paths).

## PeakBagger Research
1. Search: "{peak_name} site:peakbagger.com"
2. Extract route descriptions from peak page
3. List recent ascents with trip reports:
   ```bash
   uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak ascents {peak_id} --format json --with-tr --limit 20
  1. Identify trip reports with content (word_count > 0)

  2. Fetch content for up to 5 recent trip reports using:

    uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger ascent show {ascent_id} --format json
    

SummitPost Research

  1. Search: "{peak_name} site:summitpost.org"

  2. Use WebFetch to extract: route name, difficulty, approach, description, hazards

  3. If WebFetch fails, use the fetching ladder:

    # Fast path (httpx with browser-like headers, no browser)
    uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{url}"
    
    # If the above returns {"error": ...} or content is blocked/JS-rendered:
    uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{url}"
    
    # If --render still returns a Cloudflare challenge page, escalate (needs a display):
    uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{url}"
    

Trip Report Extraction

For each report fetched, extract:

  • date, author, route conditions, gear mentioned
  • Hazards (extract explicitly and separately):
    • Rockfall zones: location on route, conditions, timing guidance mentioned
    • Icefall/serac hazard: location, stability, pre-dawn/timing advice
    • Cornice hazard: location, buildup direction, avoidance notes
  • Terrain detail (extract if mentioned):
    • Downclimb sections: location, difficulty, rappel anchors if any
    • River/stream crossings: location, flow conditions, ford difficulty
    • Water sources: named locations, seasonal availability
    • Named camps or bivy sites: name/location, exposure notes

Output Format (return EXACTLY this JSON)

{
  "sources": ["PeakBagger", "SummitPost"],
  "route_info": [
    {"source": "...", "name": "...", "difficulty": "...", "description": "...", "hazards": [...]}
  ],
  "trip_reports": [
    {"source": "...", "date": "...", "author": "...", "url": "...", "summary": "...", "conditions": "...", "has_gpx": false,
     "rockfall": "...", "icefall": "...", "cornices": "...",
     "downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}
  ],
  "gaps": ["what couldn't be fetched and why"]
}
```"""
)

Agent 2: WTA + Mountaineers + Regional Sources

Task(
  subagent_type="general-purpose",
  model="sonnet",
  prompt="""You are a route researcher gathering mountaineering data for {peak_name}.

## Your Assignment
Research from these sources: WTA, Mountaineers.org, northwesthikers.net, hikeoftheweek.com, Oregon Hikers Field Guide (oregonhikers.org), Cascade Climbers (cascadeclimbers.com), Mountain Project

**Retrieval strategy — discover URLs, then fetch.** For each web source below (except mountaineers.org — use the Mountaineers MCP, see below), FIRST run a `site:` WebSearch (e.g. `"{peak_name} site:wta.org"`, `site:nwhikers.net`, `site:cascadeclimbers.com`) to collect the exact hike-page and individual trip-report URLs. THEN fetch each discovered URL through the fetching ladder. Do not WebFetch a guessed URL — enumerate real URLs first. This recovers reports that one-pass fetching loses to 403/JS blocks.

## WTA Research
1. Search: "{peak_name} site:wta.org"
2. Find the hike page and extract: trail name, difficulty, distance, elevation gain, hazards
3. Get trip reports from AJAX endpoint: {wta_url}/@@related_tripreport_listing
4. Fetch content for up to 5 recent trip reports using the fetching ladder:
   ```bash
   # Fast path first
   uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{trip_report_url}"

   # If output contains {"error": ...} or content is blocked/JS-rendered:
   uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{trip_report_url}"

   # If --render still returns a Cloudflare challenge page, escalate (needs a display):
   uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{trip_report_url}"

Mountaineers Research (use the Mountaineers MCP FIRST — do not scrape)

mountaineers.org reliably returns HTTP 403 to WebFetch/cloudscrape. Use the Mountaineers MCP tools instead — they return structured data:

  1. mcp__mountaineers__search_routes (query "{peak_name}") and mcp__mountaineers__get_route to get the route/place page (difficulty, directions, gear).
  2. mcp__mountaineers__search_trip_reports (query "{peak_name}") and, when you have a route URL, mcp__mountaineers__get_route_trip_reports to enumerate member trip reports.
  3. mcp__mountaineers__get_trip_report to pull each relevant report's body + structured fields (date, author, result, road/conditions notes).
  4. Only if the MCP is unavailable, document the gap — mountaineers.org reliably returns HTTP 403 to WebFetch/cloudscrape, so scraping is not a viable fallback for this domain.

Note: the Mountaineers MCP is available to Task-dispatched general-purpose agents (this agent). Extract route beta, technical requirements, and hazards from the MCP results.

NWHikers Research (northwesthikers.net / nwhikers.net)

  1. Search: "{peak_name} site:nwhikers.net OR site:northwesthikers.net"
  2. Use WebFetch to extract first-person trip reports, GPS track notes, conditions
  3. If WebFetch fails, use cloudscrape.py "{url}" (fast path usually sufficient)

Hike of the Week (hikeoftheweek.com — REQUIRES --render)

  1. Search: "{peak_name} site:hikeoftheweek.com"

  2. MUST use --render flag — site is Cloudflare-protected and blocks WebFetch:

    uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{url}"
    # If --render still returns a Cloudflare challenge page, escalate (needs a display):
    uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{url}"
    
  3. Extract: logistics, route narrative, access notes, trailhead directions

Oregon Hikers Field Guide (oregonhikers.org — Oregon objectives only)

  1. Search: "{peak_name} site:oregonhikers.org"
  2. Use WebFetch — site is static MediaWiki HTML, WebFetch-friendly
  3. Extract: route description, access, permits, conditions notes

Cascade Climbers (cascadeclimbers.com)

  1. Search: "{peak_name} site:cascadeclimbers.com"
  2. Use WebFetch; if blocked use cloudscrape.py "{url}"
  3. Extract: technical route beta, gear lists, trip reports, conditions

Mountain Project (for technical/rock sections)

  1. Search: "{peak_name} site:mountainproject.com"
  2. Use WebFetch to extract: route name, grade, gear, description, rock quality
  3. If WebFetch fails, use cloudscrape.py "{url}"

Fallback

If WebFetch fails for any page, use the fetching ladder: cloudscrape.py "{url}" (fast) → cloudscrape.py --render "{url}" for JS-rendered or Cloudflare-protected pages.

Trip Report Extraction

For each report fetched, extract:

  • date, author, route conditions, gear mentioned
  • Hazards (extract explicitly and separately):
    • Rockfall zones: location on route, conditions, timing guidance mentioned
    • Icefall/serac hazard: location, stability, pre-dawn/timing advice
    • Cornice hazard: location, buildup direction, avoidance notes
  • Terrain detail (extract if mentioned):
    • Downclimb sections: location, difficulty, rappel anchors if any
    • River/stream crossings: location, flow conditions, ford difficulty
    • Water sources: named locations, seasonal availability
    • Named camps or bivy sites: name/location, exposure notes

Output Format (return EXACTLY this JSON)

{
  "sources": ["WTA", "Mountaineers", "NWHikers", "HikeOfTheWeek", "OregonHikers", "CascadeClimbers", "MountainProject"],
  "route_info": [
    {"source": "...", "name": "...", "difficulty": "...", "description": "...", "hazards": [...]}
  ],
  "trip_reports": [
    {"source": "...", "date": "...", "author": "...", "url": "...", "summary": "...", "conditions": "...", "has_gpx": false,
     "rockfall": "...", "icefall": "...", "cornices": "...",
     "downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}
  ],
  "gaps": ["what couldn't be fetched and why"]
}
```"""
)

Agent 3: AllTrails

Task(
  subagent_type="general-purpose",
  model="sonnet",
  prompt="""You are a route researcher gathering mountaineering data for {peak_name}.

## Your Assignment
Research from AllTrails

## AllTrails Research
1. Search: "{peak_name} site:alltrails.com"
2. Use WebFetch to extract: trail name, difficulty, distance, elevation gain, route type, best season, hazards
3. If WebFetch fails, use:
   ```bash
   uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{url}"
  1. From route description and any visible reviews/comments, extract if present:
    • Rockfall zones, icefall/serac hazard, cornice hazard
    • Downclimb sections, river/stream crossings, water sources, named camps

Output Format (return EXACTLY this JSON)

{
  "sources": ["AllTrails"],
  "route_info": [
    {"source": "...", "name": "...", "difficulty": "...", "distance_miles": N, "elevation_gain_ft": N, "description": "...", "hazards": [...],
     "rockfall": "...", "icefall": "...", "cornices": "...",
     "downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}
  ],
  "trip_reports": [],
  "gaps": ["what couldn't be fetched and why"]
}
```"""
)

Execute all 3 agents in parallel by including all Task calls in a single response.

Step 3C: Aggregate Results

After Python script and all agents return, aggregate into unified data structure:

{
  "conditions": { /* from fetch_conditions.py */ },
  "route_data": {
    "sources": [ /* merged from all 3 agents */ ],
    "trip_reports": [ /* merged from all agents */ ]
  },
  "gaps": [ /* merged gaps from all sources */ ]
}

Partial Failure Handling:

  • If any agent fails entirely, proceed with data from successful agents
  • Note failed sources in the gaps array
  • Minimum viable: conditions data + at least one route source
Step 3D: Access, Permits, and Road/Gate Status (Inline)

Determine permits AND the current road/gate status to the trailhead — do not just tell the user to go check. Actively research and report the actual status with a source and date.

Permits:

WebSearch: "{peak_name} trailhead access" ; "{peak_name} permit requirements"

Road / gate status workflow (identify the access highway + forest road + managing agency first, then check sources in order; capture each source URL for the report):

  1. State DOT pass report (WA → WSDOT): fetch the relevant pass page, e.g. https://wsdot.com/travel/real-time/mountainpasses/mt.-baker (SR-542) or .../north-cascades (SR-20). Read RoadCondition / TravelAdvisoryActive / restrictions. Other states: WebSearch "{state} DOT mountain pass report {highway}".
  2. USFS forest alerts/conditions: fetch https://www.fs.usda.gov/{region}/{forest-shortname}/alerts (e.g. r06/mbs/alerts) and /conditions; search the page for the road number / trailhead name → closure milepost, reason, seasonal gate. Also WebSearch "{forest name} {road or trailhead} road open {year}" for seasonal-opening press releases.
  3. NPS road conditions (if in/through a national park): fetch https://www.nps.gov/{park-code}/planyourvisit/road-conditions.htm (e.g. noca, mora, olym) → per-road OPEN/CLOSED + milepost.
  4. WTA ground truth (PNW): WebSearch "site:wta.org {trail} gate road open closed {year}" or fetch the hike page; scan the 3-5 most recent trip reports for "gate"/"road open/closed"/"drove to" (use cloudscrape.py --render if WTA 403s).
  5. InciWeb fire closures (Jul-Oct): WebSearch "inciweb {area} closure {trailhead} {year}"; if an active incident is near the trailhead, read its closure page.

Synthesize into a dated status statement for the report's Road Conditions section:

"Gate/road status (as of {date}): {road} is {OPEN/CLOSED/SEASONAL GATE/UNKNOWN} per source. {If closed: gate at {milepost}, adds ~{N} mi each way.}"

If no source confirms it, say so explicitly and include the managing ranger station phone as the fallback. Add trailhead names, permits, the status statement, and all source URLs to route_data.

Phase 4: Route Analysis

Goal: Analyze gathered data to determine route characteristics and synthesize information.

Step 4A: Determine Route Type

Based on route descriptions, elevation, and gear mentions, classify as:

  • Glacier: Crevasses mentioned, glacier travel, typically >8000ft
  • Rock: Technical climbing, YDS ratings (5.x), protection mentioned
  • Scramble: Class 2-4, exposed but non-technical
  • Hike: Class 1-2, trail-based, minimal exposure
Step 4B: Synthesize Route Information from Multiple Sources

Goal: Combine trip reports and route descriptions from Step 3B researcher agents, plus conditions data from Step 3A, into comprehensive route beta.

Source Priority:

  1. Trip reports (Step 3B agents) - first-hand experiences
  2. Route descriptions (Step 3B agents) - published beta baseline
  3. PeakBagger/ascent data (Step 3A Python script) - basic info, patterns

Synthesis Pattern for Route, Crux, and Hazards:

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
33
Forks
1
Last commit
Jul 2026
Advanced
Catalog kind
skill
Gateway key
route-researcher
Source
github.com/dreamiurg/claude-mountaineering-skills