Skip to content

Commit 5f60f74

Browse files
julienldclaude
andauthored
fix: improve bug report tool with better diagnostics (#256)
- Add installation method detection (pyinstaller, addon, docker, git, pypi, unknown) - Add platform detection (OS, version, architecture, Python version) - Add PyPI marker file for reliable PyPI install detection - Add startup log collector (first 60 seconds of server logs) - Increase log retrieval factor from 2x to 4x for more context - Update docstring with facts-only instructions for AI agents - Clean up template with auto-populated environment section - Use HTML details/summary for collapsible log sections Closes #253 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5674a13 commit 5f60f74

4 files changed

Lines changed: 227 additions & 45 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ package-dir = {"" = "src", "tests" = "tests"}
5050
packages = { find = { where = ["src", "."], include = ["ha_mcp*", "tests"] } }
5151

5252
[tool.setuptools.package-data]
53-
ha_mcp = ["py.typed", "resources/*.md", "resources/*.json"]
53+
ha_mcp = ["py.typed", "_pypi_marker", "resources/*.md", "resources/*.json"]
5454

5555
[tool.black]
5656
line-length = 88

src/ha_mcp/_pypi_marker

Whitespace-only changes.

src/ha_mcp/tools/tools_bug_report.py

Lines changed: 157 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,72 @@
66
"""
77

88
import logging
9+
import os
10+
import platform
11+
import sys
12+
from pathlib import Path
913
from typing import Annotated, Any
1014

1115
from pydantic import Field
1216

1317
from ha_mcp import __version__
1418

15-
from ..utils.usage_logger import AVG_LOG_ENTRIES_PER_TOOL, get_recent_logs
19+
from ..utils.usage_logger import AVG_LOG_ENTRIES_PER_TOOL, get_recent_logs, get_startup_logs
1620
from .helpers import log_tool_usage
1721

1822
logger = logging.getLogger(__name__)
1923

2024

25+
def _detect_installation_method() -> str:
26+
"""
27+
Detect how ha-mcp was installed.
28+
29+
Returns one of: pyinstaller, addon, docker, git, pypi, unknown
30+
"""
31+
# 1. PyInstaller binary
32+
if getattr(sys, "frozen", False):
33+
return "pyinstaller"
34+
35+
# 2. Home Assistant Add-on (has supervisor token)
36+
if os.environ.get("SUPERVISOR_TOKEN"):
37+
return "addon"
38+
39+
# 3. Docker container (non-addon)
40+
if Path("/.dockerenv").exists():
41+
return "docker"
42+
43+
# 4. Git clone - check for .git directory relative to package
44+
try:
45+
# Go up from tools_bug_report.py -> tools -> ha_mcp -> src -> project_root
46+
project_root = Path(__file__).parent.parent.parent.parent
47+
if (project_root / ".git").exists():
48+
return "git"
49+
except Exception:
50+
pass
51+
52+
# 5. PyPI install - marker file exists in package
53+
try:
54+
marker_path = Path(__file__).parent.parent / "_pypi_marker"
55+
if marker_path.exists():
56+
return "pypi"
57+
except Exception:
58+
pass
59+
60+
# 6. Default - unknown
61+
return "unknown"
62+
63+
64+
def _detect_platform() -> dict[str, str]:
65+
"""Detect platform information."""
66+
return {
67+
"os": platform.system(), # Windows, Darwin, Linux
68+
"os_release": platform.release(),
69+
"os_version": platform.version(),
70+
"architecture": platform.machine(),
71+
"python_version": platform.python_version(),
72+
}
73+
74+
2175
def register_bug_report_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2276
"""Register bug report tools with the MCP server."""
2377

@@ -49,31 +103,33 @@ async def ha_bug_report(
49103
"""
50104
Collect diagnostic information for filing bug reports against ha-mcp.
51105
106+
**IMPORTANT FOR AI AGENTS:**
107+
When creating a bug report, you MUST only report FACTS that you directly
108+
observed during the conversation. Do NOT make assumptions or guesses.
109+
- Report exact error messages you received
110+
- Report exact tool names and parameters you used
111+
- Report exact responses from tools
112+
- Do NOT speculate about causes or solutions
113+
- Do NOT fill in template sections you cannot answer from the conversation
114+
52115
**WHEN TO USE THIS TOOL:**
53116
Use this tool when the user says something like:
54117
- "I want to file a bug for: <reason>"
55118
- "This isn't working, I need to report this"
56119
- "How do I report this issue?"
57120
58-
**BEFORE CALLING THIS TOOL:**
59-
If the bug details are unclear, guide the user to provide:
60-
1. What they were trying to do (the goal)
61-
2. What actually happened (the result)
62-
3. What they expected to happen instead
63-
4. Any error messages they saw
64-
5. Steps to reproduce the issue
65-
66-
**PARAMETERS:**
67-
- tool_call_count: Count how many ha_* tools you called since the bug started.
68-
This helps include the right amount of logs. Default is 3.
69-
70121
**OUTPUT:**
71-
Returns diagnostic info, recent logs, and a bug report template.
72-
The template guides privacy-conscious reporting while preserving
73-
enough detail to diagnose the issue.
122+
Returns diagnostic info (auto-populated), recent logs, startup logs,
123+
and a bug report template. All environment info is automatically filled.
74124
"""
125+
# Detect installation method and platform
126+
install_method = _detect_installation_method()
127+
platform_info = _detect_platform()
128+
75129
diagnostic_info: dict[str, Any] = {
76130
"ha_mcp_version": __version__,
131+
"installation_method": install_method,
132+
"platform": platform_info,
77133
"connection_status": "Unknown",
78134
"home_assistant_version": "Unknown",
79135
"entity_count": 0,
@@ -101,19 +157,26 @@ async def ha_bug_report(
101157
logger.warning(f"Failed to get entity count: {e}")
102158

103159
# Calculate how many log entries to retrieve
104-
# Formula: AVG_LOG_ENTRIES_PER_TOOL * 2 * tool_call_count
105-
max_log_entries = AVG_LOG_ENTRIES_PER_TOOL * 2 * tool_call_count
160+
# Formula: AVG_LOG_ENTRIES_PER_TOOL * 4 * tool_call_count (doubled from 2x to 4x)
161+
max_log_entries = AVG_LOG_ENTRIES_PER_TOOL * 4 * tool_call_count
106162
recent_logs = get_recent_logs(max_entries=max_log_entries)
107163

164+
# Get startup logs (first minute of server operation)
165+
startup_logs = get_startup_logs()
166+
108167
# Format logs for inclusion (sanitized summary)
109168
log_summary = _format_logs_for_report(recent_logs)
169+
startup_log_summary = _format_startup_logs(startup_logs)
110170

111171
# Build the formatted report
112172
report_lines = [
113173
"=== ha-mcp Bug Report Info ===",
114174
"",
115-
f"Home Assistant Version: {diagnostic_info['home_assistant_version']}",
116175
f"ha-mcp Version: {diagnostic_info['ha_mcp_version']}",
176+
f"Installation Method: {diagnostic_info['installation_method']}",
177+
f"Platform: {platform_info['os']} {platform_info['os_release']} ({platform_info['architecture']})",
178+
f"Python Version: {platform_info['python_version']}",
179+
f"Home Assistant Version: {diagnostic_info['home_assistant_version']}",
117180
f"Connection Status: {diagnostic_info['connection_status']}",
118181
f"Entity Count: {diagnostic_info['entity_count']}",
119182
]
@@ -124,6 +187,13 @@ async def ha_bug_report(
124187
if "time_zone" in diagnostic_info:
125188
report_lines.append(f"Time Zone: {diagnostic_info['time_zone']}")
126189

190+
if startup_logs:
191+
report_lines.extend([
192+
"",
193+
f"=== Startup Logs ({len(startup_logs)} entries) ===",
194+
startup_log_summary,
195+
])
196+
127197
if recent_logs:
128198
report_lines.extend([
129199
"",
@@ -135,7 +205,7 @@ async def ha_bug_report(
135205

136206
# Bug report template for the AI to present to the user
137207
bug_report_template = _generate_bug_report_template(
138-
diagnostic_info, log_summary
208+
diagnostic_info, log_summary, startup_log_summary
139209
)
140210

141211
# Anonymization instructions
@@ -145,14 +215,17 @@ async def ha_bug_report(
145215
"success": True,
146216
"diagnostic_info": diagnostic_info,
147217
"recent_logs": recent_logs,
218+
"startup_logs": startup_logs,
148219
"log_count": len(recent_logs),
220+
"startup_log_count": len(startup_logs),
149221
"formatted_report": formatted_report,
150222
"bug_report_template": bug_report_template,
151223
"anonymization_guide": anonymization_guide,
152224
"issue_url": "https://github.com/homeassistant-ai/ha-mcp/issues/new",
153225
"instructions": (
154226
"Present the bug_report_template to the user. "
155-
"Ask them to review and fill in the [DESCRIBE...] sections. "
227+
"The Environment section is already filled with accurate data. "
228+
"Ask the user to describe the bug, what happened, and what they expected. "
156229
"Remind them to follow the anonymization_guide to protect their privacy. "
157230
"The user should copy the completed template and submit it at the issue_url."
158231
),
@@ -182,56 +255,96 @@ def _format_logs_for_report(logs: list[dict[str, Any]]) -> str:
182255
return "\n".join(lines)
183256

184257

258+
def _format_startup_logs(logs: list[dict[str, Any]]) -> str:
259+
"""Format startup log entries for inclusion in a bug report."""
260+
if not logs:
261+
return "(No startup logs available)"
262+
263+
lines = []
264+
for log in logs:
265+
elapsed = log.get("elapsed_seconds", 0)
266+
level = log.get("level", "INFO")
267+
logger_name = log.get("logger", "")
268+
message = log.get("message", "")
269+
270+
# Truncate long messages
271+
if len(message) > 200:
272+
message = message[:200] + "..."
273+
274+
line = f" +{elapsed:05.2f}s | {level:5} | {logger_name}: {message}"
275+
lines.append(line)
276+
277+
return "\n".join(lines)
278+
279+
185280
def _generate_bug_report_template(
186-
diagnostic_info: dict[str, Any], log_summary: str
281+
diagnostic_info: dict[str, Any],
282+
log_summary: str,
283+
startup_log_summary: str,
187284
) -> str:
188-
"""Generate a bug report template for users to fill out."""
285+
"""Generate a bug report template with auto-populated environment info."""
286+
platform_info = diagnostic_info.get("platform", {})
287+
189288
return f"""## Bug Report Template
190289
191-
**Copy this template, fill in the sections, and submit at:**
290+
**Copy this template, fill in the bug description sections, and submit at:**
192291
https://github.com/homeassistant-ai/ha-mcp/issues/new
193292
194293
---
195294
196-
### I want to file a bug for: [DESCRIBE THE BUG IN ONE SENTENCE]
295+
### Bug Summary
296+
<!-- Describe the bug in one sentence -->
197297
198-
### What I was trying to do
199-
[DESCRIBE YOUR GOAL - What were you asking the AI to do?]
200298
201299
### What happened
202-
[DESCRIBE THE RESULT - What did the AI do or say?]
300+
<!-- Describe what the AI did or what error occurred -->
301+
302+
303+
### What you expected
304+
<!-- Describe what should have happened instead -->
203305
204-
### What I expected
205-
[DESCRIBE EXPECTED BEHAVIOR - What should have happened instead?]
206306
207307
### Steps to reproduce
208-
1. [First step]
209-
2. [Second step]
210-
3. [...]
308+
<!-- If you can reproduce the issue, list the steps -->
309+
1.
310+
2.
311+
3.
211312
212313
### Error messages (if any)
213314
```
214-
[PASTE ANY ERROR MESSAGES HERE]
315+
<!-- Paste any error messages here -->
215316
```
216317
217-
### Environment
218-
- Home Assistant Version: {diagnostic_info.get('home_assistant_version', 'Unknown')}
219-
- ha-mcp Version: {diagnostic_info.get('ha_mcp_version', 'Unknown')}
220-
- Connection Status: {diagnostic_info.get('connection_status', 'Unknown')}
221-
- Entity Count: {diagnostic_info.get('entity_count', 0)}
222-
- Time Zone: {diagnostic_info.get('time_zone', 'Unknown')}
318+
### Environment (auto-populated)
319+
- **ha-mcp Version:** {diagnostic_info.get('ha_mcp_version', 'Unknown')}
320+
- **Installation Method:** {diagnostic_info.get('installation_method', 'Unknown')}
321+
- **Platform:** {platform_info.get('os', 'Unknown')} {platform_info.get('os_release', '')} ({platform_info.get('architecture', 'Unknown')})
322+
- **Python Version:** {platform_info.get('python_version', 'Unknown')}
323+
- **Home Assistant Version:** {diagnostic_info.get('home_assistant_version', 'Unknown')}
324+
- **Connection Status:** {diagnostic_info.get('connection_status', 'Unknown')}
325+
- **Entity Count:** {diagnostic_info.get('entity_count', 0)}
326+
- **Time Zone:** {diagnostic_info.get('time_zone', 'Unknown')}
327+
328+
### Startup logs
329+
<details>
330+
<summary>Click to expand startup logs</summary>
331+
332+
```
333+
{startup_log_summary}
334+
```
335+
</details>
223336
224337
### Recent tool calls
338+
<details>
339+
<summary>Click to expand recent tool calls</summary>
340+
225341
```
226342
{log_summary}
227343
```
228-
229-
### Additional context
230-
[ADD ANY OTHER RELEVANT INFORMATION]
344+
</details>
231345
232346
---
233347
**Privacy note:** Please review and anonymize any sensitive information before submitting.
234-
See the anonymization guide in the tool output for details.
235348
"""
236349

237350

0 commit comments

Comments
 (0)