A layered field guide for production Copilot Studio agents, ordered by cost of investigation rather than by likelihood: reproduce, validate the configuration, read the transcript, then reach for Application Insights and the systems beyond it.
Originally published at https://beyondtheprompt.dev/posts/copilot-studio-troubleshooting-field-guide on 6 August 2026.
Audience: developers, solution architects and support engineers who own Copilot Studio agents in a production environment. Scope: agents built in Copilot Studio (classic and the new agents experience). Declarative Microsoft 365 Copilot agents are mostly out of scope - they emit different telemetry and are called out where relevant. How to use this page: work top to bottom during an incident. Each section ends with a checklist. If a checklist is clean, move on. If it isn’t, you’ve probably found your problem.
Confluence setup notes
📸. Replace them with captures from your tenant - generic Microsoft docs screenshots age badly and don’t show your environment names.Suggested page tree
| # | Section | Use it when |
|---|---|---|
| 1 | Introduction | First time reading this |
| 2 | Reproducing the issue | A ticket just landed |
| 3 | Troubleshooting inside Copilot Studio | Always start here |
| 4 | Analysing the conversation transcript | Config looks fine but behaviour is wrong |
| 5 | Application Insights troubleshooting | Transcript shows a failure but not why |
| 6 | Troubleshooting external systems | Telemetry points outside the agent |
| 7 | Determining the root cause | You know what happened |
| 8 | Validating the fix | Before you close the ticket |
| A | Common scenarios | Pattern matching |
| B | KQL cookbook | You’re in Logs and need a query |
| C | Decision tree | You’re stuck |
The ordering isn’t arbitrary. Each stage is cheaper than the one after it, and each one narrows the search space for the next. Jumping straight to KQL is the single most common time sink on these incidents - you end up staring at a dependencies table with no idea which conversation you’re looking for.
Copilot Studio agents fail in a way that traditional apps don’t. There’s no stack trace pointing at line 47. The agent “just answered wrong”, and the reason could be a trigger phrase, a badly worded instruction, a stale SharePoint index, an expired connection reference, a 429 from the model, or a downstream API that returned 200 OK with an empty body. All of those look identical to the user.
Without a process, people investigate whatever they’re most comfortable with. The .NET dev goes straight to exceptions. The Power Platform person re-publishes the agent and hopes. Both sometimes work, which is exactly what makes the habit hard to break.
The process below is ordered by cost of investigation, not by likelihood. That’s deliberate: a 30-second check that rules out 20% of causes beats a 30-minute KQL session that rules out 40%.
Before anything else, ask these four questions. In my experience they close a genuinely uncomfortable share of tickets:
| Category | Typical symptom | Where you’ll find it |
|---|---|---|
| Routing | Wrong topic fires, or nothing fires and you get the fallback | Copilot Studio topics + transcript |
| Generative behaviour | Vague answers, hallucination, refusal, wrong tone | Instructions, knowledge, transcript |
| Knowledge | “I couldn’t find that” for content you know exists | Knowledge sources, permissions, indexing |
| Tools / actions | Action silently returns nothing, or errors | Transcript + Application Insights dependencies |
| Authentication | Works for you, fails for users; 401/403 | Connection references, Entra, channel auth |
| Performance | Long pauses, client timeouts, partial answers | Application Insights durations |
| Content safety | Response blocked or replaced with a generic message | customEvents containing ContentFiltered |
| Platform | Everything broken at once, multiple agents | Service health, DLP policy, environment state |
Line these up before an incident, not during one. Chasing a Reader role at 22:00 is a bad time.
conversationtranscript table directly.Screenshot: the agent’s Settings > Advanced page showing the Application Insights section with the connection string field (redacted) and the four logging toggles.
The goal of this stage is not to fix anything. It’s to turn “the bot is broken” into a set of identifiers you can search on. If you skip this, everything downstream becomes guesswork, because you’ll be scanning telemetry with no anchor.
Get all of this before you touch a setting. Every field here maps to something you’ll filter on later.
| Field | Why you need it | Where to get it |
|---|---|---|
| Environment name + ID | Telemetry, transcripts and config are all per-environment | Power Platform admin centre, or the URL in Copilot Studio |
| Agent name + schema name / ID | Multiple agents often share an App Insights resource | Copilot Studio > agent details |
| Published version / last publish time | Tells you whether the user hit your latest changes | Copilot Studio > Publish history |
| Channel | Teams, Direct Line, custom web chat and the test pane behave differently | The user, or channelId in telemetry |
| User UPN + whether authenticated | Permission-scoped knowledge behaves per-user | The ticket |
| Timestamp with time zone | Everything in Azure is UTC. Prague is UTC+1/+2 | The ticket - and always convert to UTC yourself |
| Conversation ID | The single most valuable field you can get | See below |
| Session ID | Groups a user’s turns in agent-level telemetry | Transcript / telemetry |
| Exact prompt text | “I asked about holidays” is not a prompt | The user - ask for a copy/paste, not a paraphrase |
| Expected vs actual behaviour | Half of all “bugs” are misunderstood requirements | The ticket |
| Screenshots | Shows the channel, the rendering, and any error card | The user |
This is the field that turns a two-hour investigation into a ten-minute one. Three ways to get it:
/debug conversationid. The agent returns the ID for the current conversation.Error code: ... Conversation ID: ... directly in the message the user sees. Ask users to screenshot the whole bubble, not just the first line.Tip: add a support topic to your agents that echoes the conversation ID on request (“Something went wrong? Say support code”). Costs you ten minutes to build, saves hours per incident. Put it behind a phrase that users won’t hit accidentally.
That’s common and it’s fine. Change tactics rather than grinding:
Intermittent failures are usually one of: model throttling (429), a dependency timeout, token expiry mid-conversation, or an inconsistently indexed knowledge source. All four are visible in telemetry even when you can’t reproduce them.
Copy this into a Confluence template and attach it to every agent incident.
Everything in this section is free - no KQL, no Azure portal, no waiting for telemetry ingestion. Work through it before you open Application Insights. Realistically, a large share of incidents die here.
Start with the dullest possible questions.
Is it published, and is the published version the one you think it is? Open Publish and check the timestamp of the last successful publish. Compare it to when your change went in and when the user hit the failure. A publish that ran after the failure doesn’t help you; a publish that failed silently is worse.
Are you in the right environment? Check the environment picker and the URL. If your DEV and PRO agents have the same display name - and they usually do - this is a real trap. Verify the environment GUID.
Is the agent actually available on the channel the user used? An agent can be published and still not enabled for Teams, or published to Teams but not submitted to the org catalogue. Check Channels and confirm the specific channel shows as configured.
Is the deployment intact? If the agent arrives via solution import (ALM pipeline), check the solution imported cleanly in the target environment. Partial imports are notorious: the agent lands, but connection references or environment variables don’t get set, and everything downstream fails with authentication errors that look like a permissions problem.
Environment health Environments in an admin operation (backup, restore, copy, capacity block) behave strangely. Check the environment state in the Power Platform admin centre before you spend an hour on the agent.
Screenshot: Publish page showing publish history with timestamps and status. Screenshot: Channels page with Teams enabled and its availability state.
Common mistake: testing your fix in the test pane, seeing it work, and closing the ticket. The test pane runs the draft. Publish, then test through the same channel the user used, ideally as a user without your permissions.
Routing problems produce two symptoms: nothing fires (you get the fallback / “I’m not sure how to help with that”) or the wrong thing fires.
Global.IsManager = true will silently never fire if the variable is never populated for that channel.This distinction matters a lot and is easy to miss:
If your agent uses generative orchestration and a topic never fires, the fix is almost never “add more trigger phrases”. It’s “write a description that tells the model when to use this”. A description like “Handles leave” is far weaker than “Use when the user wants to check their remaining annual leave balance, request time off, or cancel an existing request.”
Screenshot: topic trigger configuration showing trigger phrases and the priority setting. Screenshot: a tool/topic description field in generative orchestration mode.
Open Settings > Generative AI (and the agent’s Instructions).
Instructions Read them as if you were the model and knew nothing. Look for:
Grounding and the “answer from knowledge only” setting If the agent is set to answer only from knowledge sources and the sources don’t contain the answer, the correct behaviour is a refusal. Users report this as “the bot doesn’t know anything”. That’s a content problem, not a bug. Conversely, if general model knowledge is allowed, you’ll get plausible answers about things you never documented - reported as hallucination.
Model selection Agents can run on different models, and the model can change under you when your tenant gets updated defaults. If behaviour shifted with no change on your side, check which model the agent is using - this is one of the few genuinely invisible causes of “it changed overnight”.
Content moderation / safety
Moderation levels trade off answer coverage against risk. A high setting will block borderline content and return a generic message. If users report the agent “refusing normal questions”, check moderation before you rewrite instructions. You can confirm it in telemetry - see the ContentFiltered query in Appendix B.
Screenshot: Generative AI settings showing knowledge grounding options and content moderation level.
Knowledge problems are the most common cause of “the answer is wrong” reports, and they’re rarely about the model.
Work through this order:
Testing the retrieval, not the answer Ask a question whose answer appears verbatim in exactly one document, phrased as it appears in the document. If that fails, retrieval is broken. If it succeeds but paraphrased questions fail, the content is retrievable but poorly chunked or poorly titled - a content-authoring problem.
Watch out: when SharePoint is the knowledge source, conversation transcripts include the question and the retrieved source content (
search_results) but the agent’s answer is written out asREDACTED. Don’t waste time hunting for the response text in the transcript - go to the telemetry or reproduce it live.
Screenshot: Knowledge page showing sources with their status/indexing state.
Actions (Power Automate flows, connectors, custom connectors, prompts, MCP tools) fail in a small number of predictable ways.
Inputs
Outputs
200 OK with no data is successful as far as telemetry is concerned. This is a classic invisible failure.Authentication and connection references
Error handling Does the topic handle a failed action, or does it charge on and produce a confusing message? Add explicit error branches for anything user-facing - it makes future incidents diagnosable from the transcript alone.
Screenshot: an action node showing input/output mapping. Screenshot: the connection reference configuration for a custom connector.
Multi-agent setups add a layer where things get lost.
In telemetry, sub-agent activity is traceable: the child inherits the parent’s conversation ID with a _<subConversationId> suffix, so you can reconstruct the whole tree. See B.7.
If section 3 didn’t find it, stop looking at configuration and start looking at what the agent actually did. The transcript is the cheapest source of truth you have: it shows the decisions, not the intentions.
| Source | What you get | Latency | Notes |
|---|---|---|---|
| Test pane activity trace | Full node-by-node execution for the current conversation | Immediate | Best for reproducible issues. New agents experience shows tool inputs/outputs inline |
| Test pane > Download snapshot | ZIP of the conversation + diagnostics | Immediate | Attach this to tickets |
| Monitor tab / Analytics > Download Sessions | CSV of sessions with ChatTranscript | Minutes after session timeout | Last ~29 days. Requires Bot Transcript Viewer |
Dataverse conversationtranscript via Power Apps | Full activity JSON - the richest option | Minutes | Requires Dataverse access; default retention ~30 days, configurable |
| Copilot Agent Kit - Agent Debugger | Guided analysis over stored transcripts, filterable to error conversations | Minutes | Worth installing if you support agents at scale |
Know the gaps before you rely on them: transcripts aren’t written for Dataverse-for-Teams environments, Dataverse developer environments, or Microsoft 365 Copilot agents. SharePoint-grounded answers come through
REDACTED. The Copilot Studio CSV truncates each agent response at 512 characters - for full responses, go to Dataverse.
Screenshot: Analytics page with the Download Sessions pane open. Screenshot: test pane activity trace showing nodes with expand arrows.
Work through it in this order. Resist the urge to jump to the error - the error is usually a consequence of something three steps earlier.
1. Timeline. Get the sequence of turns and the gaps between them. A four-second gap before a response is a tool call or a knowledge search. A twenty-second gap is a problem even if the answer was correct.
2. Topic selection / orchestration decision. Which topic or tool did the agent choose, and was it the right one? This single field resolves a large fraction of “wrong answer” tickets - the agent answered a different question correctly.
3. Trigger evaluation. What did the agent match on? If it matched a topic you didn’t expect, compare that topic’s trigger phrases with the user’s prompt and you’ll usually see the collision immediately.
4. Variable values. Check them at each step, particularly:
User.DisplayName, User.Id) - empty here means the user isn’t authenticated the way you assumed.5. User inputs as the agent received them. Not as the user thinks they typed them. Autocorrect, pasted formatting, and channel-level trimming all cause surprises.
6. Knowledge searches. Look at what was searched for and what came back. A search that returned nothing tells you a different story than one that returned three irrelevant documents. The first is a scope/permission/indexing problem; the second is a content problem.
7. Action execution. Inputs sent, outputs received, duration, result. Confirm the inputs match what you’d expect from the conversation.
8. Errors and fallback. Note where the fallback topic engaged. Fallback firing mid-conversation (rather than at the start) usually means an unhandled action failure.
Learn these five and you’ll diagnose most transcripts on sight:
| What you see in the transcript | What it means |
|---|---|
| An unexpected topic fired immediately | Trigger phrase collision, or a weak tool description under generative orchestration |
| Correct topic, but the agent asks a question it should already know the answer to | Variable not populated - check the previous node’s output |
| Action shows success, subsequent variable is empty | Output schema mismatch or an empty payload from the downstream system |
| Knowledge search executed, zero results | Scope, permissions, or indexing - in that order of likelihood |
| Response is generic/refusing with no tool or knowledge activity at all | Content moderation, grounding restrictions, or instructions blocking the path |
Everything you want is visible: the routing decision and why, the identity, the inputs, the outputs, the duration, the result.
Routing was correct. The action “succeeded”. The real failure is on line 3: User.Id is empty, so the tool was called with a blank employee ID and the backend returned an empty result set with a 200. The user-visible message points at knowledge; the actual cause is authentication.
This is the single most valuable pattern in this guide: result: Success does not mean the step did its job. Always check the outputs, not the status.
A caveat worth knowing: in the environment-level telemetry preview, agent and tool execution errors aren’t always reflected correctly in trace statuses. Treat
resultCodeas a hint, not as ground truth, and verify against payloads.
Screenshot: annotated activity trace with the empty variable circled and an arrow to the downstream generic response.
This is the deepest layer, and the one where people waste the most time. Used well, it’s the only place that shows you the whole picture - the agent’s decisions, every downstream call, the failure, and the timing. Used badly, it’s a very expensive way to look at rows.
Read this before you write a single query. Copilot Studio has two telemetry modes and they write to completely different tables with completely different schemas. Running a customEvents query against an environment-level export returns nothing, and you’ll conclude telemetry is broken when it’s working fine.
| Agent-level telemetry | Environment-level telemetry (preview) | |
|---|---|---|
| Configured in | Agent > Settings > Advanced > Application Insights (connection string) | Power Platform admin centre > data export package, type Copilot Studio |
| Scope | One agent | All Copilot Studio agents in the environment |
| Main table | customEvents | dependencies |
| Shape | Bot Framework-style activity events | OpenTelemetry GenAI spans |
| Key identifiers | session_Id, user_Id, customDimensions.conversationId | operation_Id (one per turn), customDimensions["gen_ai.conversation.id"] |
| Topic events | TopicStart, TopicEnd, Action etc. | Not captured |
| Tool detail | Limited | Rich: gen_ai.tool.name, .call.arguments, .call.result |
| Prerequisites | Any environment | Managed environments only; excludes declarative agents |
Don’t send both to the same Application Insights resource. Microsoft explicitly recommends against it, and in practice you’ll spend your incident trying to work out which schema a row belongs to. Pick one strategy per resource.
Two more things that will save you an hour:
invoke_agent) moved from requests to dependencies. You may still see agent root traces in the requests table until rollout completes in your region. If a query comes back empty, check the other table before concluding anything.Quickest way to find out what you actually have:
Getting this model straight in your head is most of the battle.
Environment-level (OpenTelemetry spans):
operation_Id.InvokeAgent span is the trace root. ExecuteTool and OutputMessages spans hang beneath it via operation_ParentId = the InvokeAgent span’s id.gen_ai.conversation.id._<subConversationId> suffix - split on _ to reconstruct the tree.OutputMessages spans don’t always have an InvokeAgent root, so orphaned single-node traces are normal, not a bug.Agent-level (customEvents):
session_Id groups a user session; customDimensions.conversationId identifies the conversation.TopicStart > Action/GenerativeAnswers/node events > BotMessageSend / BotMessageReceived > TopicEnd.customDimensions.designMode tells you whether it came from the test canvas. Filter this out in production queries or your metrics will be polluted by your own testing.Rather than trusting any documented field list (including this page), discover the live schema in your own resource:
Run the second query once a month and paste the output into a child page. It’s the highest-value five minutes of documentation you can do for your team.
What they represent: inbound operations against the service. Historically this is where root agent invocations landed; with the environment-level rollout they’re moving to dependencies. Check both.
What to look at:
success / resultCode - the blunt “did it work” signal.duration - end-to-end time for the operation. This is what the user feels.operation_Id - your join key into everything else.performanceBucket - pre-bucketed durations, handy for quick distribution checks.Read p95/p99, not the average. Averages hide exactly the tail that generates tickets. If p50 is 900ms and p99 is 28s, you don’t have a slow agent - you have a specific slow path, and it’s usually one dependency.
This is where you’ll spend most of your time. With environment-level telemetry, dependencies holds both the agent’s own GenAI spans (type == "GenAI") and the outbound calls the platform makes.
Things that show up here: Azure OpenAI / model calls, Dataverse operations, SharePoint and Graph queries, REST APIs via custom connectors, MCP servers, Work IQ, Power Automate flows, and your own APIs.
type == "GenAI" rows are the agent itself. Everything else is downstream. If failures cluster in one target, you’ve found the system to investigate in section 6.
Interpreting resultCode:
| Code | Usually means | Where to go next |
|---|---|---|
| 401 | Token missing, expired, or wrong audience | Connection reference, Entra app registration |
| 403 | Authenticated but not authorised | Permissions on the target resource / SharePoint item |
| 404 | Wrong URL, deleted resource, or wrong environment | Tool configuration, environment variables |
| 408 / empty + long duration | Timeout | section 5.7 Performance |
| 429 | Throttling - model quota or connector limits | Azure OpenAI capacity, connector throttling limits |
| 5xx | Downstream service fault | The service’s own logs |
ERROR with no code | Platform-side failure | Status.message in customDimensions |
The tool payloads are the most useful thing in the whole telemetry set, and they’re easy to miss because they live inside customDimensions:
Two things to look for:
{"value":[]} with a success code is the invisible failure from section 4, and this is where you prove it.Note: tool input arguments and output results are only captured when Log conversation details is enabled in the agent’s Application Insights settings. If
gen_ai.tool.call.argumentsis consistently empty across all rows, that’s a configuration gap, not a platform bug. Weigh it against your data-handling policy before enabling it in production - these payloads can contain personal data.
traces carries diagnostic log records and, for some workloads, the richest lifecycle detail.
For real-time voice agents the whole picture lives here rather than in dependencies, keyed on customDimensions.Subject:
Voice event subjects worth knowing: CallLifecycle, DialogLifecycle (DialogDuration_Ms, EndReason), SpeechPipeline (AudioLatency_Ms), LlmInvocation, ToolExecution. The top-level message field is a placeholder - always read customDimensions.
Custom telemetry. If you emit your own events from Power Automate flows or custom APIs feeding the agent, put the Conversation ID in a custom dimension. It costs nothing at write time and makes your logs joinable to the agent’s, which is worth a great deal at 02:00.
Then pivot from a specific exception to its whole trace:
Reading exceptions:
type groups the failure class. Two exceptions with the same type and different messages are usually the same bug.outerMessage is the human-readable one. details holds the stack, which matters mainly for custom code you own.Content-safety blocks show up as a specific signature rather than a classic exception:
Users report slowness as brokenness. Two very different problems hide behind it: consistently slow (an architecture issue) and occasionally very slow (a tail issue). Separate them first.
Where the time usually goes, in rough order of frequency:
The
durationcaveat:durationisn’t populated for classic agent traces in the environment-level export. If durations are all0, you’re looking at classic traces - fall back to timestamp deltas between spans, or use agent-level telemetry for that agent.
Timeouts. A dependency that ends around a suspiciously round number (30s, 60s, 120s) with no result code is a timeout, and the number tells you whose timeout: the connector’s, the gateway’s, or the platform’s. Find the component whose configured limit matches and you’ve found the layer to fix.
This is the payoff. Given a Conversation ID, you can walk the entire lifecycle.
The one query to start every investigation with:
startswith rather than == is deliberate - it picks up sub-agent conversations, which carry the _<subId> suffix.
Once you have the operation_Id for the failing turn, the end-to-end transaction view in the Azure portal (click any row > End-to-end transaction details) renders the whole span tree as a waterfall. For timing problems it’s faster than any query you’ll write.
Before you conclude “there’s no telemetry”, rule these out:
itemCount - values above 1 mean you’re seeing sampled data, and aggregate counts need multiplying.designMode == True rows are test-canvas traffic. If your “production incident” only appears in design mode rows, the incident is somebody testing.designMode == True (agent-level)operation_Idsdependencies for failures and for successes with empty resultsoperation_IdBy this point telemetry has told you which system failed. This section is about confirming it and finding out why. The general method is always the same: reproduce the call outside the agent. If a REST call fails in Postman with the same identity, the agent isn’t your problem.
| Symptom | Likely cause | Check |
|---|---|---|
| 429s in bursts | Quota / TPM limits exceeded | Azure OpenAI metrics for the deployment; request quota increase or spread load |
| Consistently slow first token | Large prompt - long instructions plus large retrieved context | Reduce instruction length, tighten retrieval |
| Sudden behaviour change with no deployment | Model version changed underneath the agent | Agent model setting; deployment model version in Azure |
| Blocked or generic responses | Content filter | ContentFiltered in customEvents; content moderation level in the agent |
| Truncated answers | Token limits reached | Response length, context size |
contains filters on large tables are slow. If a Dataverse dependency sits at 4-8 seconds, look at the FetchXML/OData being generated, not the platform.Most common source of “the answer is wrong” and it’s nearly always one of four things:
Remember that SharePoint-grounded answers appear as REDACTED in transcripts - use telemetry or live reproduction instead.
Retry-After header.gen_ai.tool.name - filter on it directly:MCP failures cluster into three types, and the debugging order matters:
1. No tools discovered. If the agent behaves as if the tools don’t exist:
tools/list actually return tools? Test with MCP Inspector outside Copilot Studio - this single step resolves most MCP tickets.2. Tools discovered but never called. That’s a description problem, not a connectivity problem. See section 3.5.
3. Tools called and failing.
az ad sp create --id <server_app_client_id>).Work through it in this order - it goes from most to least likely:
Before deep-diving, check the boring infrastructure facts: is the resource running, is it in the expected region, has a firewall or private endpoint changed, is there an active Azure Service Health advisory, and did anything deploy recently. Check the resource’s Activity log - an unannounced config change by another team is a genuinely common root cause.
A root cause is not “the API failed”. It’s the answer to why the API failed, stated precisely enough that someone can prevent it happening again.
Test your candidate root cause against three questions:
If you can’t answer all three, keep going. Closing a ticket on a symptom means you’ll see it again in a fortnight with a different reporter.
Copy this into a Confluence template. Attach the completed version to the ticket and link it from the agent’s known-issues page.
Symptoms: From 14:10 UTC, roughly 40% of users asking about leave balances received “I couldn’t find that information right now.” Others got correct answers. No recent publish.
Evidence: Transcript for
ConvId a1b2...showed the correct tool selected,User.Idempty, andGetLeaveBalancereturning{}with a success result. Application Insights showed the same pattern across 213 turns, all from the custom web chat channel; Teams traffic was unaffected. The HR API logged 200s with empty result sets for blank employee IDs.Root cause: The Direct Line token issued by the custom web chat host stopped including the user claim after a change to the host application’s token endpoint at 14:05 UTC. The agent’s tool therefore received an empty employee ID and the HR API returned an empty set with a 200, which the agent surfaced as a generic “not found”.
Contributing factors: (1) The tool had no validation on a required input. (2) The HR API returns 200 for unknown IDs rather than 400/404. (3) No alert existed for a spike in empty tool results.
Resolution: Host application token endpoint reverted at 15:40 UTC. Input validation added to the topic to fail explicitly when
User.Idis empty.Preventative actions: Alert on empty
gen_ai.tool.call.resultrate; explicit error branch on the topic; note added to the channel onboarding checklist.
Note what the root cause statement does: it names the component, the change, the time, and the mechanism by which a working system produced a wrong answer. “Authentication issue” would have been useless.
Deploying a change is not resolving an incident. Four things need to be true before you close.
Give it 24 hours of real traffic and check:
Pattern matching. Find the closest symptom, work the steps.
Symptoms: User asks something the agent should handle; gets the fallback or a generic answer. The topic exists and looks correct.
Likely causes: trigger phrases too far from real user language, another topic winning on priority, a trigger condition that’s never true, agent not published, under generative orchestration, a weak topic/tool description.
Investigate:
Resolution: add trigger phrases in the user’s actual language (classic), or rewrite the description to state when to use the topic (generative). Adjust priority where topics genuinely overlap. Publish and retest in the real channel.
Symptoms: Agent answers a different question, confidently and correctly.
Likely causes: overlapping trigger phrases, one topic with very broad phrases acting as a magnet, priority ordering, descriptions that overlap in scope.
Investigate:
Resolution: narrow the greedy topic, remove duplicate phrases, and make descriptions mutually exclusive. Where two topics are genuinely close, consider merging them and branching inside.
Symptoms: Agent answers from the wrong document, or cites something outdated.
Likely causes: multiple documents covering the same topic (old and new both indexed), poor chunking on long documents, scope wider than intended, outdated content still live in SharePoint.
Investigate:
Resolution: archive superseded content, tighten source scope, improve document titles and headings. Model changes won’t fix a content problem - this is the one people most often try to solve with prompt engineering, and it never works.
Symptoms: Confident, plausible, wrong. Often about policies or numbers.
Likely causes: general model knowledge allowed when it shouldn’t be, knowledge gap plus instructions that push toward always answering, retrieval returning weakly relevant content, a question the content genuinely doesn’t cover.
Investigate:
Resolution: restrict to knowledge sources where accuracy matters more than coverage; add an explicit instruction to decline when the answer isn’t in the sources; fill the content gap. Accept that a stricter agent will refuse more often - that’s the trade, and it’s usually the right one for policy and HR content.
Symptoms: Works for you, fails for users. Or worked yesterday, fails today. 401/403 in dependencies.
Likely causes: expired connection, connection reference unbound after solution import, newly granted permissions not reflected in an existing token, delegated vs application permission mismatch, the channel can’t complete the sign-in flow.
Investigate:
dependencies | where resultCode in ("401","403") for the affected window.Resolution: rebind or recreate the connection (recreating forces a fresh token - necessary after permission changes), fix consent, correct the permission model. If the channel blocks the popup, that’s a host application fix, not an agent fix.
Symptoms: Agent says something went wrong, or gives a generic answer after a pause.
Likely causes: bad or missing inputs, output schema mismatch, downstream error, unhandled failure path.
Investigate:
ExecuteTool span: arguments, result, resultCode, duration.Resolution: fix input descriptions so the model populates them correctly, correct the schema, and add explicit error handling so the next occurrence is diagnosable from the transcript alone.
Symptoms: No error. The agent behaves as though it got no data - because it didn’t.
Likely causes: downstream returns 200 with an empty payload, output schema mismatch means nothing binds, a filter in the downstream query excludes everything.
Investigate:
gen_ai.tool.call.result in the ExecuteTool span - is it empty or {"value":[]}?Resolution: validate required inputs in the topic before calling; add an explicit branch for empty results with a useful message. Consider asking the API owner to return 404 rather than 200-with-nothing.
Symptoms: Tools that used to work aren’t called at all, or fail immediately.
Likely causes: server down or endpoint changed, authentication not completed (many servers return no tools until sign-in), DLP policy blocking the custom connector, cross-tenant service principal missing, tenant isolation.
Investigate:
tools/list return tools?Resolution: restore the server, re-authorise, add a DLP exception (or move the work to an environment where the connector is permitted), or have a tenant admin provision the service principal.
Symptoms: Work IQ-backed answers empty for some users, fine for others.
Likely causes: licensing, connection not authorised for that user, permission-scoped data genuinely not visible to them.
Investigate: compare a working and non-working user’s licence and group membership; check the connection state; filter telemetry on the Work IQ tool name and compare results per user.
Resolution: fix licensing or permissions. If the data genuinely isn’t visible to that user, the agent is behaving correctly - say so in the ticket, and consider whether the agent should explain why it can’t answer rather than returning nothing.
Symptoms: Long pause, then a generic error or a truncated response. Intermittent, worse at peak.
Likely causes: quota/TPM limits (429s), very large prompts, regional capacity pressure.
Investigate:
resultCode, and specifically 429 counts over time.Resolution: request quota, spread load, shorten instructions, tighten retrieval so less context is passed. Add retry/backoff where you control the call.
Symptoms: “The bot is really slow.” Sometimes correct answers, sometimes client-side timeouts.
Investigate:
Resolution: parallelise or eliminate tool calls, cache stable reference data, reduce context size, push slow work into an async pattern with an acknowledgement message. Set expectations in the UI - a typing indicator changes the perception of six seconds considerably.
Symptoms: Different users get different answers to the same question. Often reported as inconsistency, not as a permissions bug.
Investigate: compare group membership and licences between a working and non-working user; verify permission-trimming behaviour on the knowledge source; test as an affected user, not as yourself.
Resolution: fix the permissions at source, or accept and explain the behaviour - an agent that says “you don’t have access to that document” is far better than one that says “I couldn’t find anything”.
Symptoms: Specific capabilities fail while everything else works.
Investigate: identify the failing target in dependencies; reproduce outside the agent with the same identity; check the API’s own logs and any gateway (APIM) in front of it; look at status codes over time to distinguish a hard failure from throttling.
Resolution: fix the API, the credentials, or the gateway policy - then add error handling in the agent so the next occurrence produces a useful message rather than a shrug.
Symptoms: Exactly what it says.
Almost always one of: the agent isn’t published, connection references point at the maker’s connections in DEV but nothing in PRO, environment variables didn’t carry across the solution import, the user has different permissions than the maker, the channel differs, a DLP policy applies in PRO but not DEV.
Investigate: compare the two environments component by component - publish state, connection references, environment variables, DLP policies, knowledge source URLs. In an ALM pipeline, check the deployment settings file: unset connection references are the number-one cause.
Resolution: fix the deployment configuration, and add a post-deployment smoke test so the pipeline catches it next time instead of a user.
Run these in Application Insights > Logs. Placeholders are in <angle brackets>.
Before you start: confirm whether the environment uses agent-level (
customEvents) or environment-level (dependencies) telemetry - section 5.1. Queries are marked [env] or [agent] accordingly. Unmarked ones work with either.
Troubleshoots: “there’s no telemetry” reports. Use when: starting on an unfamiliar environment.
Expect: rows concentrated in customEvents (agent-level) or dependency (environment-level). Nothing at all means a configuration problem, not an agent problem.
Troubleshoots: no Conversation ID in the ticket. Use when: you have an agent name and a rough time.
Expect: one row per conversation. Sort by Failures to jump straight to the broken ones.
Variations: filter on UserName or user.email when you know the reporter.
Troubleshoots: everything. Use when: you have a Conversation ID. This is the workhorse.
Expect: the conversation in order, root span before its children, one operation_Id per turn.
Variations: == instead of startswith to exclude sub-agents; add | where resultCode != "OK" to see only failures.
Troubleshoots: agent-level equivalent of B.2. Use when: you have a session ID or user.
Expect: TopicStart > node/action events > BotMessageSend > TopicEnd.
Variations: swap session_Id for | where customDimensions contains "<conversationId>".
Troubleshoots: platform-level failures. Use when: triaging a broad outage.
Expect: empty on a healthy day. Take Sample into B.6 to see one in full.
Troubleshoots: the most common failure class. Use when: any tool or downstream issue.
Variations: | summarize count() by target, resultCode for the shape of the problem instead of the detail.
Troubleshoots: “what happened in this turn?” Use when: you have an operation_Id.
Expect: the full span tree plus any exception, in order. This is the query that usually ends the investigation.
Troubleshoots: multi-agent handoffs. Use when: a connected agent is involved.
Expect: root spans plus a nested set per child agent. Known quirk: sub-agent spans currently parent to the invoking InvokeAgent span rather than to the InvokeAgent inside their own trace - the tree looks flatter than it logically is.
Troubleshoots: “the agent is slow”. Use when: performance complaints.
Expect: the worst offenders with their operation_Ids. Feed those into B.6.
Variations: replace the where with a percentile summary by hour to see whether it’s getting worse.
Troubleshoots: which downstream call is the bottleneck.
Expect: a short list. Usually one row dominates, and that’s your fix.
Troubleshoots: throttling and model latency.
Expect: Throttled should be zero. Any sustained non-zero value is a capacity conversation.
Watch for: 429s (service protection limits) and p95 above ~2s (query shape, or a synchronous plugin).
Troubleshoots: blocked or replaced responses. Use when: users report the agent refusing normal questions.
Variations: add | where customDimensions contains "<conversationId>" to confirm a specific conversation was affected.
Expect: EmptyResult = YES alongside a success code is the “found nothing” signature - a scope, permission or indexing problem.
Expect: failures concentrated on specific users points at licensing or permissions, not the connector.
Expect: if a known MCP tool has zero rows, it isn’t being discovered or isn’t being selected - see A.8.
Read it this way: many failures across many users = configuration. Many failures for one user = that user’s permissions or consent.
Use when: the reporter can’t remember what they asked, which is most of the time.
Use when: establishing a baseline (do this before your next incident) or proving a fix worked.
Use when: starting a morning triage, or after a release. Run it daily and you’ll spot regressions before users do.
Use when: a documented field returns nothing. The schema evolves; this is the only source of truth that can’t go stale.
Why the order is what it is: each stage is cheaper than the next and narrows the search for it. Config checks cost seconds. Transcripts cost a minute. KQL costs ten. Chasing an external system owner costs a day. Work left to right and you’ll rarely need the expensive end.
This page goes stale. Copilot Studio ships changes continuously and the telemetry schema is explicitly documented as evolving.
| Review | Frequency | Owner |
|---|---|---|
| Re-run B.20 and update field references | Monthly | |
| Refresh screenshots after major UI changes | Quarterly | |
| Add new scenarios to Appendix A from closed incidents | After every RCA | |
| Verify the KQL cookbook still runs clean | Quarterly | |
| Check for changes to environment-level telemetry (still preview at time of writing) | Quarterly |
If you fix something that isn’t in Appendix A, add it. The appendix is the part of this guide that pays for itself.