troubleshoot

作者: microsoft

調查意外的聊天代理行為,透過分析JSONL檔案中的直接除錯日誌。當使用者詢問為何發生某件事、為何請求速度慢等情況時使用。

npx skills add https://github.com/microsoft/vscode --skill troubleshoot

Troubleshoot

Purpose

This skill investigates and explains unexpected agent behavior in the current Copilot agent session using its on-disk event log.

Use this skill for questions like:

  • Why did this request take so long?
  • Why was a tool called (or not called)?
  • Why did an instruction/skill/agent file not load?
  • Why did a tool call fail?
  • Why did the model not follow expectations?

Base every conclusion on evidence from the event log. Do not guess.

Locating the Session Log

The skill runs inside the session's agent, so the log is on the same machine. It lives outside the workspace — read it via the terminal (run_in_terminal), never grep_search.

  1. If a Session log: path is provided with this message, use it. It may point to a session other than the current one (via #session) and may be comma-separated paths — investigate all of them and compare.
  2. Sticky reference: if no path is on the current message but an earlier turn in this conversation already established a target (a Session log: path, or a #session: reference), keep analyzing that same session for the follow-up. Do not fall back to self-discovery here — the newest log is the current session, which is the wrong one. Switch only if the user references a new session.
  3. Otherwise, self-discover it: pick the most recently modified events.jsonl under ${XDG_STATE_HOME:-$HOME}/.copilot/session-state/<sessionId>/ — this skill is appending to the current session's log, so it's reliably newest. Honor XDG_STATE_HOME, else $HOME.
  4. If none exists there, this isn't a Copilot session — tell the user the skill supports Copilot sessions only, and stop.

Data Source — events.jsonl

Each line is a JSON object sharing one envelope:

{ "type": "...", "id": "...", "parentId": "...", "agentId": "...", "timestamp": "ISO-8601", "data": { } }
  • type — the event kind (see below).
  • id — unique event id.
  • parentId — the chronologically preceding event, not a logical parent. It is a flat back-pointer over every event, not the user → turn → tool-call hierarchy. Do not treat it as a logical parent.
  • agentId — present for sub-agent events; absent for the main agent and session-level events.
  • timestamp — ISO-8601 time. Compute durations by differencing timestamps (e.g. a tool's start vs. its completion, a turn's assistant.turn_start vs. the assistant.message).
  • data — type-specific payload.

Event types (data.* fields)

  • session.start (once) — selectedModel, reasoningEffort.
  • user.message — content; transformedContent (expanded prompt, when it differs).
  • assistant.turn_start / assistant.turn_end — turnId; measure a turn's duration vs the following assistant.message.
  • assistant.message — model, outputTokens, content, reasoningText (thinking, when present), turnId, parentToolCallId (set ⇒ sub-agent turn spawned by that tool call).
  • tool.execution_start — toolName, toolCallId, arguments, parentToolCallId (set ⇒ nested/sub-agent).
  • tool.execution_complete — toolCallId (matches start), success, result. Pair with its start by toolCallId for the full call + duration.
  • session.shutdown (once, at end) — modelMetrics[*].usage, totalNanoAiu. Absent mid-session.
  • Other (hook.*, permission.*, system.message) — inspect data as needed.

Reconstructing the flow

Iterate records in order and rebuild the logical tree from context:

  • session.start is the root.
  • a user.message begins a turn.
  • an assistant.message answers the current user.message — unless it has data.parentToolCallId, in which case it belongs to the sub-agent spawned by that tool call.
  • a tool.execution_start belongs to the current assistant.message — unless it has data.parentToolCallId, in which case it is nested under that parent tool call.
  • pair each tool.execution_start with its tool.execution_complete by toolCallId.

Secondary Source — Agent Host Wire Log (protocol communication)

events.jsonl is the primary source and answers almost every question on its own. A separate log captures the transport/protocol between VS Code and the agent host process — the JSON-RPC-style frames that drive sessions.

Use the wire log only when the symptom points at the agent host / transport itself, not the model or a tool. Reach for it when:

  • the agent host won't start, or the session never begins;
  • requests hang or time out, or the agent appears stuck "connecting" / unresponsive;
  • createSession / subscribe fails, or expected updates/notifications never arrive;
  • you see RPC / protocol / connection errors.

Do not open it for ordinary "why did the model/tool do X" questions — events.jsonl already answers those. If events.jsonl fully explains the behavior, stop there and don't read the wire log.

It is written by VS Code on the client machine, so it is only reachable for a local agent host (VS Code and the agent on the same machine). For a remote agent host it lives on the client, not the host this skill runs on — skip it there.

Location

<VS Code user-data dir>/logs/<session-timestamp>/ahp/ahp-<timestamp>-<connectionId>.jsonl

The VS Code user-data dir depends on the build:

  • Windows: %APPDATA%\Code (Insiders: Code - Insiders; OSS/dev may use Code - OSS or a custom --user-data-dir)
  • macOS: ~/Library/Application Support/Code
  • Linux: ~/.config/Code

A new logs/<timestamp>/ folder is created per VS Code session — pick the most recently modified ahp/ahp-*.jsonl.

Named by connection id, not session id — one log multiplexes all sessions on that connection (no per-session file). Long connections rotate into ahp-….1.jsonl, .2.jsonl, … (max 5); the active one is the most recently modified (the locate commands pick it). Read the newest log for connection-level health; to isolate one session, filter by its id (the session-state/<sessionId>/ folder name, which appears in frames' params/result).

If no ahp/ folder exists, the wire log is disabled (it is off by default). Tell the user to set "chat.agentHost.ahpJsonlLoggingEnabled": true, restart VS Code, reproduce the issue, then re-run.

Format

Each line is a JSON-RPC frame plus an _ahpLog envelope:

{ "jsonrpc": "2.0", "id": 12, "method": "createSession", "params": {}, "_ahpLog": { "ts": "ISO-8601", "dir": "c2s", "connectionId": "…", "transport": "local" } }
  • _ahpLog.dir — direction: c2s = VS Code → host (requests/notifications), s2c = host → VS Code (results/errors/actions/notifications).
  • _ahpLog.transport — local / websocket / ssh / wsl.
  • Requests/notifications carry method + params; responses carry result or error — pair a response to its request by id.
  • _ahpLog.truncated: true marks frames whose large payloads were elided.

Triage: look for error frames, requests (c2s with an id) that have no matching s2c response (hangs/timeouts), or a missing s2c after createSession / subscribe. Apply the same streaming / jq / node rules as for events.jsonl.

Locating it (terminal)

  • macOS/Linux: ls -t ~/Library/Application\ Support/Code*/logs/*/ahp/ahp-*.jsonl ~/.config/Code*/logs/*/ahp/ahp-*.jsonl 2>/dev/null | head -n 1
  • Windows (PowerShell): Get-ChildItem "$env:APPDATA\Code*\logs\*\ahp\ahp-*.jsonl" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1

Tooling Strategy (important)

The event log is outside the workspace, so grep_search cannot read it. Use run_in_terminal.

Do a single triage pass first (important for efficiency)

Each terminal command is a separate round-trip, so don't run one command per question. Do one streaming pass (constant memory — events.jsonl can be hundreds of MB, so never load it whole) that prints, in a single read:

  • the event counts by type,
  • every tool call with success/failure and duration (pair each tool.execution_start with its tool.execution_complete by toolCallId),
  • the user messages.

Use a streaming jq program (macOS/Linux) or a streaming Node readline pass (Windows) — the per-query examples below are the building blocks. Then run targeted follow-ups only to drill in.

macOS / Linux / WSL / Git Bash (grep / jq)

  • Locate the newest log: ls -t "${XDG_STATE_HOME:-$HOME}"/.copilot/session-state/*/events.jsonl | head -n 1
  • Check size first: ls -lh <logPath>
  • Errors: grep '"success":false' <logPath> (tool failures) and grep '"type":"tool.execution_complete"' <logPath>
  • Count events by type (streaming): jq -nr 'reduce inputs as $i ({}; .[$i.type] += 1) | to_entries | sort_by(-.value)[] | "\(.value) \(.key)"' <logPath>
  • Tool calls: jq -c 'select(.type=="tool.execution_start") | {tool:.data.toolName, id:.data.toolCallId}' <logPath>
  • User messages: jq -c 'select(.type=="user.message") | .data.content' <logPath>
  • Assistant turns: jq -c 'select(.type=="assistant.message") | {model:.data.model, out:.data.outputTokens}' <logPath>

Windows (PowerShell + Node.js)

Do not parse the log with PowerShell JSON. Get-Content … | ForEach-Object { ConvertFrom-Json } (or any per-line ConvertFrom-Json) deserializes one object at a time and is slow even on small logs. Use Select-String for plain-text matches and a streaming node pass for anything that needs JSON. Use jq instead if it is available.

  • Locate the newest log:
    $stateHome = if ($env:XDG_STATE_HOME) { $env:XDG_STATE_HOME } else { $HOME }
    Get-ChildItem (Join-Path $stateHome '.copilot\session-state\*\events.jsonl') | Sort-Object LastWriteTime -Descending | Select-Object -First 1
    
  • Check size first: (Get-Item <logPath>).Length
  • Plain-text matches (fast, streaming, no parsing): Select-String '"success":false' <logPath>
  • JSON queries — a streaming node pass (constant memory, safe for hundreds-of-MB logs; pass the log path as an argument so it stays quote-safe). Use this shape and change only the per-line if (…) to select what you need (tool failures, tool calls, assistant turns, …): node -e "const rl=require('readline').createInterface({input:require('fs').createReadStream(process.argv[1])});rl.on('line',l=>{if(!l)return;let x;try{x=JSON.parse(l)}catch{return}if(x.type==='tool.execution_complete'&&x.data.success===false)console.log(x.data.toolCallId,JSON.stringify(x.data.result))})" "<logPath>" For aggregates (e.g. counts by type), accumulate into an object as you go and print it on rl.on('close', …).
  • For a very large log, narrow with Select-String first to find the line, then read that small slice with read_file.

General rules

  • One streaming pass, not many. Each command is a round-trip; prefer the single triage pass, then targeted follow-ups. Never load a whole (hundreds-of-MB) log into memory (readFileSync / Get-Content) — stream (jq / the readline pass) or narrow with grep / Select-String first. Check size first if unsure (ls -lh / (Get-Item).Length).
  • Never deserialize JSON line-by-line in the shell (PowerShell ConvertFrom-Json in a loop) — slow. Use one jq filter or one node pass.
  • Use read_file only for small targeted ranges, never an entire log.

Investigation Workflow

  1. Locate events.jsonl and run the single triage pass.
  2. Match the symptom: errors → success:false; latency → gaps between assistant.turn_start and its assistant.message, or a tool's start↔complete; tool / model / input → the relevant event type; agent-host / transport failure (local) → also the Wire Log.
  3. Read only the relevant slices, then determine the most likely root cause (order contributing factors by impact) and give concrete next steps.

Response Guidelines

Cover what happened and why (root cause), key evidence (paraphrased — quote only a telling detail like an error message, never raw dumps), and how to fix it. A short combined explanation is fine; add structure (headers, bullets, tables) only for complex, multi-factor issues. Keep paragraphs short.

Abstraction: don't narrate your investigation or use internal terms (events.jsonl, tool.execution_start, parentId, "envelope"). Refer to "the session log" abstractly. Focus on what happened and why, not how you found it.

Example — instead of "I read events.jsonl and saw a tool.execution_complete with success:false…", say:

The "testing" skill was found but not loaded because the folder name and the name in the skill file differ (testing vs testing2). Fix: make them match (rename the folder or change the name), then start a new session.

Important Rules

  • Base every claim on log evidence — never assume causality.
  • Search via run_in_terminal; never grep_search, and never read_file a whole (possibly huge) log — narrow first, then read small ranges.
  • If no Copilot session log exists, say so and stop.

來自 microsoft 的更多技能

oss-growth
microsoft
開源增長駭客角色
agent-framework-azure-ai-py
microsoft
使用Microsoft Agent Framework Python SDK(agent-framework-azure-ai)构建Azure AI Foundry代理。适用于使用AzureAIAgentsProvider创建持久化代理、使用托管工具(代码解释器、文件搜索、网络搜索)、集成MCP服务器、管理对话线程或实现流式响应。涵盖函数工具、结构化输出和多工具代理。
development
airunway-aks-setup
microsoft
在AKS上設定AI Runway——從裸叢集到執行模型。涵蓋叢集驗證、控制器安裝、GPU評估、供應商設定及首次部署。時機:「設定AI Runway」、「上線AKS叢集」、「安裝AI Runway」、「airunway設定」、「部署模型至AKS」、「在AKS上進行GPU推論」、「在AKS上設定KAITO」、「在AKS上執行LLM」、「在AKS上使用vLLM」、「在AKS上設定模型服務」、「AI Runway控制器」。
devops
appinsights-instrumentation
microsoft
使用Azure Application Insights檢測Web應用程式的指南。提供遙測模式、SDK設定與組態參考。適用時機:如何檢測應用程式、App Insights SDK、遙測模式、什麼是App Insights、Application Insights指南、檢測範例、APM最佳實踐。
devops
applicationinsights-web-ts
microsoft
使用Application Insights JavaScript SDK(@microsoft/applicationinsights-web)為瀏覽器/Web應用程式進行檢測。適用於真實使用者監控(RUM)——頁面檢視、點擊、AJAX/fetch依賴、例外、自訂事件,以及與後端OpenTelemetry追蹤關聯的瀏覽器端GenAI代理追蹤。涵蓋SDK載入器指令碼與npm設定、框架擴充(React、React Native、Angular)、點擊分析、遙測初始化器,以及從瀏覽器發出的代理/工具/模型span的OTel GenAI語意慣例。
devops
azure-ai-anomalydetector-java
microsoft
使用適用於 Java 的 Azure AI 異常偵測器 SDK 建置異常偵測應用程式。在實作單變量/多變量異常偵測、時間序列分析或 AI 驅動監控時使用。
development
azure-ai-language-conversations-py
microsoft
使用 azure-ai-language-conversations Python SDK 實作對話語言理解(CLU)。當使用 ConversationAnalysisClient 分析對話意圖與實體、建置 NLP 功能,或將語言理解整合至應用程式時使用。
development
azure-ai-ml-py
microsoft
Azure Machine Learning SDK v2 for Python。用於機器學習工作區、作業、模型、資料集、計算資源與管線。 觸發詞:「azure-ai-ml」、「MLClient」、「workspace」、「model registry」、「training jobs」、「datasets」。
development