{"record":{"id":"f8701079f396a9e4","repo":"mvanhorn/last30days-skill","slug":"no-cluster-matched-target-r-available-clusters","errorCode":null,"errorMessage":"No cluster matched {target!r}. Available clusters: {candidates}","messagePattern":"No cluster matched (.+?)\\. Available clusters: (.+?)","errorType":"exception","errorClass":"DrillTargetError","httpStatus":null,"severity":"warning","filePath":"skills/last30days/scripts/lib/planner.py","lineNumber":245,"sourceCode":"def _drill_cluster_text(report: schema.Report, cluster: schema.Cluster) -> str:\n    candidates = {candidate.candidate_id: candidate for candidate in report.ranked_candidates}\n    parts = [cluster.title]\n    for candidate_id in cluster.candidate_ids:\n        candidate = candidates.get(candidate_id)\n        if candidate:\n            parts.extend((candidate.title, candidate.snippet))\n    return \" \".join(part for part in parts if part)\n\n\ndef resolve_drill_clusters(report: schema.Report, target: str) -> list[schema.Cluster]:\n    \"\"\"Resolve a 1-based cluster index or fuzzy title/entity description.\"\"\"\n    cleaned = target.strip()\n    numeric = re.fullmatch(r\"(?:cluster\\s*)?#?(\\d+)\", cleaned, flags=re.IGNORECASE)\n    if numeric:\n        index = int(numeric.group(1))\n        if 1 <= index <= len(report.clusters):\n            return [report.clusters[index - 1]]\n        raise DrillTargetError(target, report.clusters)\n\n    target_entities = entity_extract.extract_text_entities(cleaned)\n    scored: list[tuple[float, schema.Cluster]] = []\n    for cluster in report.clusters:\n        cluster_text = _drill_cluster_text(report, cluster)\n        title_score = relevance.token_overlap_relevance(cleaned, cluster.title)\n        body_score = relevance.token_overlap_relevance(cleaned, cluster_text)\n        entity_score = entity_extract.entity_overlap(\n            target_entities,\n            entity_extract.extract_text_entities(cluster_text),\n        )\n        score = max(title_score, (0.75 * body_score) + (0.25 * entity_score))\n        scored.append((score, cluster))\n\n    scored.sort(key=lambda entry: entry[0], reverse=True)\n    if not scored or scored[0][0] < 0.35:\n        raise DrillTargetError(target, report.clusters)\n    return [scored[0][1]]","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/planner.py#L227-L263","documentation":"DrillTargetError (a ValueError subclass) from resolve_drill_clusters when the target parses as a 1-based cluster index (e.g. '3', 'cluster 3', '#3') but the number is outside 1..len(report.clusters). The error carries the target and the report's clusters so its message can list available candidates.","triggerScenarios":"Calling build_drill_plan / resolve_drill_clusters with target='5' on a report whose clusters list has fewer than 5 entries; target='0' (index is 1-based, so 0 never matches); large indices after re-running with shallower depth produced fewer clusters.","commonSituations":"User asks to drill into cluster 7 from a previous report but the current report has 4 clusters; stale cluster numbers quoted from an earlier session; off-by-one from assuming 0-based indexing.","solutions":["Use an index between 1 and len(report.clusters) — clusters are 1-based.","Or pass the cluster title / entity description instead of a number; fuzzy matching will resolve it.","Re-check the current report's cluster count before drilling."],"exampleFix":"# before\nplan = build_drill_plan(report, target=\"7\")  # report has 4 clusters\n\n# after\nplan = build_drill_plan(report, target=str(len(report.clusters)))\n# or drill by title:\nplan = build_drill_plan(report, target=\"AI agent frameworks\")","handlingStrategy":"validation","validationCode":"import re\nm = re.fullmatch(r\"(?:cluster\\s*)?#?(\\d+)\", target.strip(), re.IGNORECASE)\nif m and not (1 <= int(m.group(1)) <= len(report.clusters)):\n    raise SystemExit(f\"cluster index must be 1..{len(report.clusters)}\")","typeGuard":null,"tryCatchPattern":"try:\n    plan = build_drill_plan(report, target=t)\nexcept DrillTargetError as exc:\n    print(f\"valid targets: 1..{len(report.clusters)} or cluster titles\")\n    raise","preventionTips":["Clusters are 1-based — never use 0.","Bounds-check numeric targets against len(report.clusters) before drilling.","Prefer exact cluster titles; numbers go stale between report regenerations."],"tags":["planner","drill","cluster","input-validation"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}