
Log Troubleshooting
- 1 installs
- 5 repo stars
- Updated August 4, 2026
- comisai/comis
Debugging guide for analyzing and resolving Comis log errors and system issues.
About
Provides log parsing and troubleshooting patterns for Comis system. Developers and ops use it to diagnose and fix application errors.
- Log pattern analysis and common error resolution
- Troubleshooting decision tree
Log Troubleshooting by the numbers
- 1 all-time installs (skills.sh)
- Ranked #489 of 596 Debugging skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/comisai/comis --skill log-troubleshootingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 5 |
| Last updated | August 4, 2026 |
| Repository | comisai/comis ↗ |
What it does
Debugging guide for analyzing and resolving Comis log errors and system issues.
Files
Log Troubleshooting
You have read-only access to the daemon logs directory at ~/.comis/logs/.
This skill bundles scripts/log-digest.py -- a Python CLI that parses NDJSON logs and produces structured, context-friendly summaries. Use it as your primary analysis tool. It uses only Python 3 standard library (no pip install needed).
All script paths below are relative to this skill's directory. Resolve them against the <location> shown in the available skills listing (e.g., if the skill location is ~/.comis/skills/log-troubleshooting, then scripts/log-digest.py means ~/.comis/skills/log-troubleshooting/scripts/log-digest.py).
Quick Start
For most troubleshooting requests, start here:
python3 scripts/log-digest.py ~/.comis/logs/daemon.logThis produces a structured summary: entry count, time range, level distribution, top modules, every error and warning as a compact one-liner, slowest operations ranked by duration, and unique error messages with counts. Read the output and report findings to the user -- this single command answers most "what's wrong?" questions.
Log Location and Rotation
| File | Description |
|---|---|
~/.comis/logs/daemon.log | Active log (current session) |
~/.comis/logs/daemon.1.log | Most recent rotated log |
~/.comis/logs/daemon.2.log .. daemon.5.log | Older rotated logs |
Rotation triggers at 10MB per file, 5 rotated files kept. Start with daemon.log (current) unless investigating a past incident.
Log Format
Each line is a single JSON object (NDJSON / Pino structured logging).
Pino level codes:
| Code | Name | Meaning |
|---|---|---|
| 10 | TRACE | Finest granularity, rarely enabled |
| 20 | DEBUG | Internal steps, tool calls, intermediate state |
| 30 | INFO | Boundary events: started, stopped, request complete |
| 40 | WARN | Degraded but functional |
| 50 | ERROR | Broken functionality |
| 60 | FATAL | Unrecoverable, daemon cannot continue |
Standard fields:
| Field | Type | When Present |
|---|---|---|
level | number | Always (Pino level code) |
time | string | Always (ISO 8601) |
module | string | Always (agent, daemon, channels, gateway, memory, skills, scheduler, sub-agent-runner, graph-coordinator) |
msg | string | Always (human-readable message) |
agentId | string | Agent-scoped operations |
traceId | string | Request-scoped (auto-injected) |
durationMs | number | Timed operations |
toolName | string | Tool execution logs |
method | string | RPC/HTTP method |
err | object/string | Error details (Pino serialized) |
hint | string | Actionable fix guidance (on WARN/ERROR) |
errorKind | string | Classification: config, auth, dependency, timeout, validation, internal |
channelType | string | Channel adapter logs |
Analysis Workflow
Log files can be 10MB with 10K+ lines -- reading one raw would flood your context window and waste tokens without helping the user. The digest script solves this by parsing the JSON server-side and returning only the structured summary, so you get the full picture in a few dozen lines. Start broad with the script, then narrow with filters or grep.
Stage 1 -- Get the overview
Run the digest script with no filters to understand the full picture:
python3 scripts/log-digest.py ~/.comis/logs/daemon.logThe output includes:
- Total entry count and time range
- Level distribution (how many errors vs info vs debug)
- Top modules by log volume
- Every error and warning with compact one-liner format
- Slowest operations ranked by duration
- Unique error/warn messages with occurrence counts
This is enough to answer most questions. Read the output and summarize for the user.
Stage 2 -- Drill down
Once you know what area to investigate, use the script's filters to narrow:
By severity -- only warnings and above:
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --level warnBy module -- isolate a subsystem:
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --module agentBy time window -- what happened in a specific period:
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --after "2026-03-20T14:00:00Z" --before "2026-03-20T15:00:00Z"By keyword -- search the msg field:
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --search "timeout"Last N lines -- recent activity only:
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --tail 500Slow operations -- custom threshold:
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --slow 5000Filters can be combined:
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --module agent --level warn --after "2026-03-20T14:00:00Z"Stage 3 -- Raw output for deep inspection
When you need the actual JSON entries (e.g., to examine the full err object or trace a specific request):
Compact one-liners (good for scanning):
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --level error --compactRaw JSON (full entry data):
python3 scripts/log-digest.py ~/.comis/logs/daemon.log --level error --rawStage 4 -- Targeted grep for specific patterns
For very specific lookups where you already know what you're looking for, use grep directly on the log file. This is faster than the script for single-pattern searches:
grep '"errorKind":"auth"' ~/.comis/logs/daemon.log
grep '"agentId":"my-agent"' ~/.comis/logs/daemon.log
grep '"Comis daemon started"' ~/.comis/logs/daemon.logCommon Investigation Patterns
| Symptom | Command |
|---|---|
| General health check | python3 scripts/log-digest.py ~/.comis/logs/daemon.log |
| Daemon won't start | python3 scripts/log-digest.py ~/.comis/logs/daemon.log --module daemon --tail 50 |
| LLM not responding | python3 scripts/log-digest.py ~/.comis/logs/daemon.log --search "LLM" --level warn |
| Auth/token failures | python3 scripts/log-digest.py ~/.comis/logs/daemon.log --search "auth" --level warn |
| Bad config | python3 scripts/log-digest.py ~/.comis/logs/daemon.log --search "config" --level warn |
| Slow responses | python3 scripts/log-digest.py ~/.comis/logs/daemon.log --slow 30000 |
| Channel disconnects | python3 scripts/log-digest.py ~/.comis/logs/daemon.log --module channels --level warn |
| Find restart boundaries | grep '"Comis daemon started"' ~/.comis/logs/daemon.log |
| Shutdown issues | python3 scripts/log-digest.py ~/.comis/logs/daemon.log --search "shutdown" --level warn |
Reporting
When reporting findings to the user:
1. Lead with the summary -- how many errors, over what time range, which modules affected 2. Group repeated errors by message and show the count, not each occurrence 3. Always include the hint field when present -- it contains actionable fix guidance written by the developers 4. For slow operations, show the duration and what the operation was 5. If the user needs to take action, be specific about what to do based on the hint and errorKind
#!/usr/bin/env python3
"""Digest daemon NDJSON logs into LLM-friendly summaries.
Usage:
# Full summary (errors, warnings, timeline, slow ops)
python3 log-digest.py ~/.comis/logs/daemon.log
# Errors and warnings only
python3 log-digest.py ~/.comis/logs/daemon.log --level warn
# Filter by module
python3 log-digest.py ~/.comis/logs/daemon.log --module agent
# Time window
python3 log-digest.py ~/.comis/logs/daemon.log --after "2026-03-20T16:00:00Z"
# Raw filtered lines (pipe to clipboard or file for LLM)
python3 log-digest.py ~/.comis/logs/daemon.log --raw --level error
# Last N lines
python3 log-digest.py ~/.comis/logs/daemon.log --tail 200
# Compact one-liner per entry
python3 log-digest.py ~/.comis/logs/daemon.log --compact --level warn
"""
import argparse
import json
import sys
from collections import Counter
LEVEL_NAMES = {10: "TRACE", 20: "DEBUG", 30: "INFO", 40: "WARN", 50: "ERROR", 60: "FATAL"}
LEVEL_FROM_NAME = {v.lower(): k for k, v in LEVEL_NAMES.items()}
def parse_args():
p = argparse.ArgumentParser(description="Digest daemon logs for LLM analysis")
p.add_argument("logfile", help="Path to NDJSON log file")
p.add_argument("--level", default=None, help="Minimum level: trace/debug/info/warn/error/fatal")
p.add_argument("--module", default=None, help="Filter by module name")
p.add_argument("--after", default=None, help="Only entries after this ISO timestamp")
p.add_argument("--before", default=None, help="Only entries before this ISO timestamp")
p.add_argument("--search", default=None, help="Search msg field (case-insensitive substring)")
p.add_argument("--raw", action="store_true", help="Output filtered lines as JSON (for LLM context)")
p.add_argument("--tail", type=int, default=None, help="Only process last N lines")
p.add_argument("--slow", type=int, default=1000, help="Threshold (ms) for slow operations (default: 1000)")
p.add_argument("--compact", action="store_true", help="Compact output: one line per entry with key fields only")
return p.parse_args()
def read_lines(path, tail=None):
if tail:
with open(path, "r") as f:
lines = f.readlines()
return lines[-tail:]
else:
with open(path, "r") as f:
return f.readlines()
def parse_entry(line):
try:
return json.loads(line)
except json.JSONDecodeError:
return None
def matches_filters(entry, args, min_level):
if entry is None:
return False
level = entry.get("level", 0)
if min_level is not None and level < min_level:
return False
if args.module and entry.get("module") != args.module:
return False
if args.after and entry.get("time", "") < args.after:
return False
if args.before and entry.get("time", "") > args.before:
return False
if args.search and args.search.lower() not in entry.get("msg", "").lower():
return False
return True
def compact_line(entry):
"""One-line summary: time level module msg + key fields."""
t = entry.get("time", "?")[:19]
lvl = LEVEL_NAMES.get(entry.get("level", 0), "?")
mod = entry.get("module", "-")
msg = entry.get("msg", "")
extras = []
for k in ("durationMs", "err", "hint", "errorKind", "agentId", "toolName", "method", "channelType"):
if k in entry:
val = entry[k]
if isinstance(val, dict):
val = val.get("message", str(val)[:80])
extras.append(f"{k}={val}")
extra_str = f" [{', '.join(extras)}]" if extras else ""
return f"{t} {lvl:5s} [{mod}] {msg}{extra_str}"
def print_summary(entries, args):
if not entries:
print("No matching log entries found.")
return
level_counts = Counter(LEVEL_NAMES.get(e.get("level", 0), "UNKNOWN") for e in entries)
module_counts = Counter(e.get("module", "unknown") for e in entries)
times = [e.get("time", "") for e in entries if e.get("time")]
time_range = f"{times[0]} -> {times[-1]}" if times else "unknown"
print(f"=== Log Digest ({len(entries)} entries) ===")
print(f"Time range: {time_range}")
print()
print("Level distribution:")
for lvl in ("FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"):
if level_counts.get(lvl, 0) > 0:
print(f" {lvl:6s}: {level_counts[lvl]}")
print()
print(f"Top modules (of {len(module_counts)}):")
for mod, count in module_counts.most_common(10):
print(f" {mod}: {count}")
print()
problems = [e for e in entries if e.get("level", 0) >= 40]
if problems:
print(f"=== Errors & Warnings ({len(problems)}) ===")
for e in problems:
print(compact_line(e))
print()
slow = [e for e in entries if e.get("durationMs", 0) >= args.slow]
if slow:
slow.sort(key=lambda e: e.get("durationMs", 0), reverse=True)
print(f"=== Slow Operations (>= {args.slow}ms, showing top 20) ===")
for e in slow[:20]:
print(compact_line(e))
print()
error_msgs = Counter()
for e in entries:
if e.get("level", 0) >= 40:
msg = e.get("msg", "unknown")
error_msgs[msg] += 1
if error_msgs:
print(f"=== Unique Error/Warn Messages ===")
for msg, count in error_msgs.most_common(20):
print(f" [{count}x] {msg}")
print()
def main():
args = parse_args()
min_level = LEVEL_FROM_NAME.get(args.level.lower()) if args.level else None
lines = read_lines(args.logfile, tail=args.tail)
entries = []
for line in lines:
entry = parse_entry(line.strip())
if matches_filters(entry, args, min_level):
entries.append(entry)
if args.raw:
for e in entries:
print(json.dumps(e, ensure_ascii=False))
elif args.compact:
for e in entries:
print(compact_line(e))
else:
print_summary(entries, args)
if __name__ == "__main__":
main()