Copilot Playbook
Updated November 7, 2026
Managed App Handbook · Chapter 7 of 8

Beyond the chat, and running the app

The ms CLI, coding agents, repository modes, pipelines and environments, then owning the app after launch and measuring it with Application Insights.

For makers, developers, admins and owners · 23-minute read · Facts as of 7 November 2026

Chapter 7 of 823-minute readParts 22–23bFacts current as of 7 November 2026

Evidence tagsD DocumentedO ObservedC CommunityI Inference

Every claim in this handbook carries a tag, so you can tell documentation from observation from opinion.

  • DDocumented. Documented by Microsoft on Learn, Support, Message center, GitHub or an official blog, as of the dates in Appendix E
  • OObserved. Observed first-hand on 7 November 2026 in a live Copilot Code cloud session: its tools, its sandbox, an example tenant's policy verdicts, and the repository of a production partner-sales app
  • CCommunity. Reported by the community (MVP blogs, forums, videos, GitHub issues) and not confirmed by Microsoft
  • IInference. Inference or recommendation by the author; test it before you rely on it

Part 22

Beyond the chat: the ms CLI, coding agents and pipelines

  • What it isWorking outside the chat: the ms CLI, binding data from the command line, deploys and rollbacks, coding-agent plugins, repository modes, GitHub Actions and the SDK.
  • What you doSet up one workstation, pin the CLI version, always deploy the exact commit you tested, and choose the repository mode on day zero.
  • Watch outPackage names are changing, local runs use live data, and switching repository modes means creating a new app.

Decide this in the setup Step 1: Start

Code and Cowork cover most of an app's life, but some jobs belong outside the chat: a release pipeline, pinned tool versions, your own GitHub repository, third-party coding agents, or debugging locally against real data. Everything here works on the same app record and repository as the chats, so you can move between them freely. D

22.1

Set up a workstation once

Microsoft Learn asks for Node.js LTS 24.11.0 or later, Git 2.27.0 or later and Git Credential Manager; the plugin README says Node 22 is enough. Use the stricter baseline. Any editor works. D

node --version
git --version
git credential-manager --version
npm i -g @microsoft/managed-apps-cli
ms --version
ms auth login --account you@contoso.com
ms auth status

The package is being renamed. Learn still shows @microsoft/managed-apps-cli; the new repository advertises @microsoft/cmr-cli. Install one, record its version in the repository's README, and don't mix old and new names in one project. DC

22.2

Create, adopt or clone an app

  • New app from the command line: ms app create inventory --display-name "Inventory" creates it in your personal developer environment, provisioning one on first use. Add --environment-id <guid> to target another environment; that choice is made at creation. D
  • An existing web project: use ms app init --display-name "Inventory" --repo native in its folder rather than create, which expects an empty destination. D
  • An app someone built in Cowork, Code or Copilot Studio: the owner grants you edit access (ms app share colleague@contoso.com --access edit), then you run ms app list --permission edit and ms app clone --app <app-id> ./inventory-local. The CLI finds the repository from the app record and sets up Git authentication. Give clone an explicit folder; two Learn pages disagree about the default. D

The CLI starter is a lean React and TypeScript project on Vite. Apps created from the chats start from a richer template with Tailwind, Radix-based components and the device controls described in Part 4, so don't assume one matches the other. DO

22.3

The local inner loop

ms app dev starts your framework's dev server and a local configuration server (port 8080, app on localhost:3000) and prints a local play URL once both respond. Open that URL in the browser profile that is already signed in to your tenant; raw localhost does not reproduce the managed host. Hot reload works, and Ctrl+C stops the loop. D

ms app dev
ms app dev --local-app-url http://localhost:5173 --port 8081
ms app dev --config-only --local-app-url http://localhost:5173

Local does not mean mocked. Data calls still go through the host to live connections with your consent and permissions, so a local test can change real records. Point development at test lists or tables. DI

22.4

Binding data from the command line

Discover first, then bind. The current flag is --connector; older examples use --api-id. In non-interactive mode a tabular connector needs --as table or --as action. D

ms connector list --search sharepoint --include-blocked
ms connector list-actions --connector shared_sharepointonline
ms app add data-source --connector shared_sharepointonline --as table --connection-id <id> --dataset "https://contoso.sharepoint.com/sites/Operations" --table "Requests"
ms app add data-source --connector shared_commondataserviceforapps --as table --dataverse-environment-id <guid> --table account
ms app add data-source --connector shared_a365copilotchatmcp --as action
ms app refresh data-source
ms app show
  • Excel Online (Business) binds as a table through an interactive picker; let the picker discover the workbook's dataset id instead of pasting a SharePoint file URL. D
  • Binding a SQL stored procedure is not supported; bind tables or views. D
  • Binding Work IQ generates typed services, not a finished conversation. Your code still opens a session, reuses its id, calls tools and re-initializes when the session expires. D
  • After a schema change in the source, run ms app refresh data-source, rebuild and retest; after a credential expires, add the data source again with a new connection. D

22.5

Build, pin, deploy and roll back

git add .
git commit -m "Release inventory validation"
git push
SHA=$(git rev-parse HEAD)
ms app build --commit "$SHA"
ms app build-status --commit "$SHA" --show-log
ms app play --mode preview --commit "$SHA"
ms app deploy --commit "$SHA"
ms app play --mode live
ms app deploy --commit <previous-good-sha>
  • Push before you ask for a build: the platform builds what is in the repository, never your uncommitted files. D
  • Preview shows the latest build of main unless you pin a commit; live is a snapshot that never updates itself. Always pass the SHA you tested, because Learn pages disagree on whether an omitted commit means HEAD or the latest main. D
  • Rolling back is redeploying an older SHA. It restores code, not data: records the newer version wrote stay written. DI
  • For an app created with --repo none, ms app deploy builds and uploads from your machine, and --artifact ./release.zip uploads a prebuilt bundle. External artifacts are off by default and need admin approval. D
  • The only documented app setting is the host header: ms app set-setting --show-header false. There is no documented place for secrets or environment variables, so don't look for one. D
  • Every command takes --json, --non-interactive and --no-color for scripting. --cloud accepts public, usgov, usgovhigh, usgovdod and china, which are routing values, not proof the service runs in each cloud. CLI telemetry is on by default and excludes your code and data; ms telemetry disable turns it off. D

22.6

Coding agents: GitHub Copilot CLI, Claude Code and others

Microsoft's walkthrough uses the standalone GitHub Copilot CLI (run it in VS Code's terminal if you like) with a plugin marketplace. Start a fresh agent session after installing. D

copilot
/plugin marketplace add microsoft/Copilot-Managed-Runtime
/plugin install copilot-managed-runtime@Copilot-Managed-Runtime

Claude Code takes the same two marketplace commands. Learn's quickstart still shows the archived identity (microsoft/Managed-Apps and microsoft-managed-apps@Managed-Apps), which is a separate install rather than an upgrade. Codex can load the portable package for evaluation only, and no tested Cursor recipe was published as of this edition. D

Slash commandWhat it does
/create-appChecks prerequisites, plans and scaffolds an app, then starts the local loop
/dev and /playRun the local loop; open the hosted preview or live app
/deployBuilds and releases, with a confirmation step
/share and /share-linkGrant or revoke access; create, list or revoke organization links
/list-apps and /delete-appFind and inspect apps; delete one
/add-data-source and /list-connectorsGeneric binding and connector discovery
/add-sharepoint, /add-dataverse, /add-excelBind tables from those services
/add-office365, /add-office365-usersOutlook operations; user and organization lookups
/add-teams, /add-onedrive, /add-azuredevopsMessaging, files, work items and pipelines
/add-mcscopilot, /add-workiqCopilot Studio agent actions; Work IQ
/add-backendA feature-gated backend definition workflow; scaffolding, not provisioning

The plugin wraps the CLI with 22 skills and does not ship an MCP server of its own. Its conventions are worth copying even in Code: plan significant changes, require a green build, sync source before releasing, and ask before deploying, sharing or deleting. It keeps project memory in a memory-bank.md file at the repository root. D

22.7

Repository modes: decide before you create

ModeWhat you getWatch out for
Platform Git (native, the default)A Microsoft-hosted repository; edit access grants clone and push; the easiest handoff between Cowork, Code, Studio and the CLINo documented pull-request review, branch protection or CI hooks
Your GitHub (github)An empty repository in a GitHub Enterprise Cloud organization with the Microsoft Managed Apps GitHub app installed; your pull requests and branch rules applyApp access does not grant GitHub access; GitHub Enterprise Server, personal repositories and Azure DevOps repositories are not supported
No repository (none)Source lives wherever you like; you upload built artifactsCan't be cloned or edited from the chats; you own source review and CI

Switching modes later means creating a new app, so choose on day zero. For a GitHub-bound app, ms app create inventory --repo https://github.com/contoso/inventory links it and ms git auth refresh renews the Entra-to-GitHub mapping. D

22.8

Release pipelines with GitHub Actions

Microsoft's documented pipeline needs an app created with --repo none, a committed ms.config.json, external artifacts allowed by your admin, and an Entra service principal with a client secret stored in Actions secrets. D

  • In environments with Dataverse, the application user needs the System Administrator and System Customizer roles; in environments without Dataverse it needs EnvironmentAdmin. Use the Enterprise Applications object id: the App Registrations id fails with "400 Principal not found." This is the published setup, not a least-privilege design. D
  • The action input app-id is the service principal's client id, not the app's id, which comes from ms.config.json. D
  • The new repository's actions are referenced at @main and old release tags were not carried over; Learn still shows microsoft/Managed-Apps/...@v1. Pin a reviewed commit SHA before you depend on it. DI
- uses: microsoft/Copilot-Managed-Runtime/github-actions/install-ms-cli@main
- uses: microsoft/Copilot-Managed-Runtime/github-actions/ms-app-pack@main
  with:
    working-directory: apps/inventory
    app-id: ${{ secrets.PP_SP_CLIENT_ID }}
    client-secret: ${{ secrets.PP_SP_CLIENT_SECRET }}
    tenant-id: ${{ secrets.PP_SP_TENANT_ID }}
- uses: microsoft/Copilot-Managed-Runtime/github-actions/ms-app-deploy@main

There is no Microsoft-published Azure Pipelines recipe and no federated-credential variant yet; anything else is your own design and your own risk. D

22.9

Environments and promotion

New apps land in the maker's personal developer environment, which is a Managed Environment governed by routing rules, not a dev stage in a pipeline. There is no ms app move. Microsoft documents moving a Cowork app to a shared or production environment: you need its id and maker rights there, you must share it again afterwards, and Dataverse tables do not move with it. D

For real dev, test and production separation, run separate apps with separate data bindings, promote a reviewed SHA or artifact between them, and keep a release manifest of app ids, environments, bindings and versions. Within one app, preview is a release candidate and live is a snapshot; neither is an isolated data store. Feature switches hide work but are not security: enforce restricted writes in the data source. I

Decide this in the setup Step 7: Access and licensing

22.10

Inside the app code

  • The SDK has no root import. Use its subpaths: /app (getContext, setConfig), /auth (getUser), /data (getClient) and /telemetry. D
  • getUser() returns the signed-in person's fullName, objectId and tenantId, supplied by the host; the app never handles a token. Email address, theme, host navigation and storage APIs are not documented for managed apps. D
  • Don't edit ms.config.json or generated/ by hand; the tools own them. Everything else is ordinary code. D
  • The default content security policy sets worker-src 'none' and manifest-src 'none', so offline use and installable app behavior are off the table; responsive layout is up to you. D
  • For telemetry, add Application Insights' web SDK, register a logger through setConfig, and ask your admin to add the exact ingestion origin to connect-src. Telemetry starts only after the app loads, so failed launches show up only in the admin center's monitoring. D

22.11

Migrating older apps

Re-check after Ignite
  • App Builder agent apps stop opening on 30 November 2026. From 4 November, one-click Migrate app moves screens, navigation and logic into Apps in Copilot Studio only. Moving to Cowork, or anywhere else, is a manual rebuild from the downloaded source. Nothing migrates into Copilot Code. Sharing links don't carry over; data stays where it is. Keep the original until the new app is verified. D
  • vibe.powerapps.com apps move one-click to Apps in Copilot Studio by the same date. Publish draft tables first, because unpublished tables don't migrate; manual rebuilds can't yet reach Dataverse tables in the default environment, and Studio ZIP uploads are limited to 10 MB. D
  • Power Apps code apps have no official converter. Community ports keep the React components, swap the Power Apps SDK and plugin for the managed-apps ones, register with ms app init, regenerate connectors and redeploy. C

Part 23

Running the app after launch

  • What it isOwning an app after launch: succession, health checks, cost, change management and retirement.
  • What you doName an owner and a backup, schedule a weekly check, watch credit use, and block an app before you delete it.
  • Watch outChatty apps drive the runtime bill, and App Builder apps need action before 30 November 2026.

Decide this in the setup Step 9: Quality and operations

23.1

Ownership and succession

Name an owner and a backup owner in README.md, give the backup edit access, and review both every quarter. No co-owner model or ownership transfer is documented for managed apps, and the inventory's Owner column can differ from the person who built the app. DI Deleting is permanent, and the two delete routes treat the repository differently (see the callout in section 15.6). DO

23.2

Health and support

  • Schedule a weekly check in Cowork: open the app, compare a few totals with the source, read the feedback list and report the differences. I
  • Connections age out. Ask Code to check connection status (connected, stale, expired) before every release and after password or policy changes. O
  • Use the admin center's monitoring for open success and latency, and Application Insights for deeper telemetry, which needs a content security policy entry. D
  • Add a "Send feedback" control that writes to a SharePoint list, and triage it weekly. I

23.3

Cost

Re-check after Ignite
ActivityWho paysHow to watch it
Building in CoworkThe builder's Copilot credits, under the Cowork spending policy/cost in the task D
Building in CodeThe builder's Copilot credits, under the Code spending policy, with per-user spend limitsCopilot cost management. A /cost command is not documented for Code I; builders report that it works there C
Building in Copilot StudioThe environment's Copilot Studio billing, from the first draft, set up in the Power Platform admin centerPower Platform admin center
Building with the CLINo license needed, except to run the app locallyNot applicable
Running a managed appEach user: Power Apps Premium, or 0.1 credit per API call (launches, connector reads and writes), plus a per-launch charge whose rate is not published, under a managed-apps spending policy DCopilot, Cost management
Work IQ calls from an appCopilot credits, billed separately even for Premium usersThe same spending policy, and admin billing reports

The bill is driven by chatty apps: at pay-as-you-go pricing of about one cent per credit, a call costs roughly a tenth of a cent, plus a per-launch charge whose rate is not published, so cache reads, avoid polling and load data on demand. DI

23.4

Change management

  • Publish release notes, keep a "What's new" page in the app, and update Help whenever behavior changes. O
  • Keep a dated known-issues backlog with an owner for each item. O
  • Tell users before planned pauses; when an admin blocks an app, people simply can't open it.

23.5

Retirement

Re-check after Ignite

Block before you delete, archive the repository, record the sunset date and tell the users. DI If you still have apps built with the older App Builder agent, act now. App Builder and vibe.powerapps.com retire on 30 November 2026. From 4 November, one-click Migrate app moves screens, navigation and logic into Apps in Copilot Studio only. Moving to Cowork, or anywhere else, is a manual rebuild from the downloaded source. D

Part 23b

Telemetry with Azure Application Insights

  • What it isIn-app telemetry for a managed app with Azure Application Insights: what it records, how to set it up, and how to feed the results back into the app and the next release.
  • What you doCreate the resource, have Code add the web SDK, keep the connection string in a settings list, ask your admin for one connect-src origin, then review a weekly summary.
  • Watch outThe admin has to add the ingestion origin to the content security policy. Never send names, emails or record contents, and remember failed launches never reach Application Insights.

Decide this in the setup Step 9: Quality and operations

23b.1

Why add telemetry, and what you get

The admin center's monitoring shows whether apps open and how fast. History is kept 28 days, with up to five alert rules per tenant, and only admins see it. D It can't tell you which screen people abandon, which call is slow or whether a new feature helped. Application Insights can, and the builder can see it too.

You get these signals: D

  • Two built-in metrics from the platform. The app SDK passes them to your logger: a session load summary (did the app open, and how long until it was usable) and network request summaries (URL, method, duration, status, size).
  • Browser telemetry from the web SDK: page views, fetch and XHR dependency calls with timings, unhandled exceptions, and any custom events you send.
  • Usage tools: Users, Sessions and Events views, Funnels (up to six steps), User Flows, Cohorts, and workbook templates for retention and user impact.
  • Logs, workbooks and alerts over all of it, written in Kusto Query Language (KQL).

Telemetry starts only after the app loads. A blocked or failed launch never reaches Application Insights. It shows up only in the admin center's monitoring, so keep watching both. D

23b.2

Before you start: who does what

RoleDoesNeeds
Builder (app owner)Writes the Code prompt, chooses the events, tests, runs the weekly reviewEdit access to the app; Reader on the Application Insights resource
Azure ownerCreates the Application Insights resource and its Log Analytics workspace, sets the daily cap, grants ReaderAn Azure subscription and a cost center
Power Platform or Microsoft 365 adminAdds one origin to connect-src for the app's environment groupA request built with the exception recipe below
Flow owner (service account)Runs the weekly summary flowA premium license, because Azure Monitor Logs is a premium connector D

Settle first: one Azure region for data residency, one resource per app, and who may see the results.

23b.3

Set up, step by step

  1. Create the resource. In the Azure portal, create a workspace-based Application Insights resource in your chosen region, then copy its connection string from Overview. The string carries a regional ingestion endpoint, which keeps ingestion in that geography. D
  2. Keep the string out of the blueprint and the skill. Microsoft says the connection string "isn't considered a security token or key". It is still visible in every user's browser. D Managed apps have no documented place for secrets or environment variables (section 22.5). D So keep it in one row of a SharePoint "App settings" list that the app reads at startup, with an on/off column. That keeps the string out of the repository. The owner can also switch telemetry off without a release. I The blueprint records only the host, in integrations.externalHosts (for example <region>.in.applicationinsights.azure.com), never the string itself.
  3. Add the SDK with Code. Use the prompt below. Code bundles the npm package @microsoft/applicationinsights-web. Don't use the CDN loader script: the default script-src blocks third-party CDNs, and bundling means only the ingestion origin needs an exception. DI
  4. Find the origin. Publish the build with telemetry switched on for yourself only. Open the app, press F12, filter the console to Errors and look for "Refused to connect". Copy that origin exactly. D
  5. Ask for the exception. Send the request below. Ask for connect-src only, for that one origin, never a wildcard. D
  6. Test with a colleague. After the change propagates (several minutes), reload. Check that the console shows no violations and that events appear in Logs within a few minutes. Then switch telemetry on for one colleague and confirm their session shows up without their name in it. DI
Add Azure Application Insights telemetry to this app. Install @microsoft/applicationinsights-web and bundle it; do not use the CDN loader. Read the connection string and an Enabled flag from the "App settings" SharePoint list at startup; if the row is missing or Enabled is false, do nothing and never throw. Initialize with disableAjaxTracking true, samplingPercentage from the settings row (default 100) and no authenticated user context. Register one logger with setConfig from @microsoft/managed-apps/app (types from @microsoft/managed-apps/telemetry) that forwards each platform metric with trackEvent, after stripping query strings and record ids from any URL. Add a small track(name, props) helper that only accepts the event names and property keys in src/telemetry/events.ts, and call trackPageView with the screen name on every route change. Never send names, emails, user ids, free text or record contents. Put it behind the feature switch "telemetry", off by default. Add tests for the helper and the URL scrubber, then show me the diff before publishing.

Fetch tracking stays on and covers the app's own calls. Turning off XHR tracking avoids duplicate entries. Adjust it if your build relies on XHR. I

The exception request uses the standard recipe:

There is no self-service exception request, so make the admin's decision easy. Send one message with these fields, and ask for the narrowest change, at the group level. DI

FieldWhat to write
AppName, app id, environment and environment group
Change requestedThe exact connector and action, MCP server, or content security directive and origin (copy the blocked origin from the browser console)
WhyThe business justification: the outcome and who benefits
DataClassification of the data that will flow, and its system of record
Scope and durationGroup level or narrower; permanent or until a date; report-only mode first for content security changes
EvidenceReproduction steps, the blocked origin or error from the browser console, and test results from a preview build
OwnerBuilder, backup owner and the admin contact who approved it

On report-only mode: Microsoft recommends enforcing a change in a development or test environment first, then running report-only in production before enforcing it there. D Report-only applies to the whole policy, not to one origin. So for a single telemetry origin, the narrowest route is usually adding it in a test environment group first, then in production. Let your admin choose. I

23b.4

What to track

Decide the events before you write code, and keep the list in src/telemetry/events.ts. I

SignalHowName and propertiesNever include
Screen viewstrackPageView on route changeScreen name, for example pipelineRecord titles or ids in the name
Key actionstrackEventreport_exported, request_submitted; properties: screen, outcome, item count bandFree text, customer names
ErrorsAutomatic exceptions plus trackException in catch blocksError code, screen, connector nameStack data that echoes field values
Slow callsPlatform network metric and dependency timingsConnector or route, duration, statusFull URLs with filters or ids
Feature switch exposuretrackEvent once per session per switchfeature_exposed; flag, state (on or off), releaseWho is in which ring
App openPlatform session load summarySuccess, time to interactiveNothing extra
FeedbacktrackEvent when the form is sentfeedback_sent; rating onlyThe comment (it stays in the feedback list)

Use bands rather than raw values where a value could identify someone: "1–5 items", not "3 items for account 4412". I

23b.5

Privacy and cost

As of 7 November 2026: D

  • IP addresses. By default, Application Insights uses the IP address for a city-level location lookup, then stores 0.0.0.0 in place of the address. Leave IP masking on.
  • User ids. Users are counted with anonymous ids in browser cookies, so a person on two browsers counts twice. Don't call setAuthenticatedUserContext with a real id. If you must link sessions, use a salted one-way hash of the object id, and record that decision with your privacy lead. DI
  • Sampling. The web SDK sends 100 percent of events by default. Lower samplingPercentage if volume grows. Sampling makes funnels and the retention workbooks less accurate.
  • Retention. Application Insights tables keep data for 90 days at no charge. You can extend that per table at extra cost.
  • Residency. Data stays in the region of the resource and its workspace.
  • Cost. Ingestion is billed per GB through the workspace, at a rate that varies by region. The Azure pricing page shows the first 5 GB a month per billing account free on the pay-as-you-go Analytics Logs tier. Check the calculator for your region, and ask the Azure owner to set a daily cap.

For sizing, 200 users with three sessions a day and 40 events per session, at about 1 KB per event, comes to about 0.7 GB a month. That fits inside the free allowance if nothing else in the billing account uses it. I Telemetry posts go straight to Azure, not through the app's connectors. We expect them not to count as runtime API calls, but Microsoft doesn't document this. I

23b.6

Reading the results

Open the resource, then Monitoring > Logs. Platform metrics land in customEvents. D Platform metric names have appeared in more than one form in Microsoft's samples, so start with the first query. DI

// 1. Which event names is this app sending?
customEvents
| where timestamp > ago(7d)
| summarize count() by name
| order by count_ desc
// 2. App open time, 75th percentile by day (successful opens only)
customEvents
| where name == "sessionLoadSummary"
| extend ok = tobool(customDimensions.successfulAppLaunch),
         tti = todouble(customMeasurements.timeToAppInteractive)
| where ok == true
| summarize p75_ms = percentile(tti, 75) by bin(timestamp, 1d)
| render timechart
// 3. Slowest calls by route, 75th percentile
customEvents
| where timestamp > ago(7d) and name == "networkRequest"
| extend url = tostring(split(tostring(customDimensions.url), "?")[0]),
         duration = todouble(customMeasurements.duration)
| summarize calls = count(), p75_ms = percentile(duration, 75) by url
| top 10 by p75_ms
// 4. Screens by visits and distinct browsers
pageViews
| where timestamp > ago(28d)
| summarize views = count(), browsers = dcount(user_Id) by name
| order by views desc
// 5. Top errors this week
exceptions
| where timestamp > ago(7d)
| summarize count = count(), sessions = dcount(session_Id) by problemId, screen = tostring(customDimensions.screen)
| top 10 by sessions
// 6. Did the switched-on feature help? Completion rate by switch state
let exposed = customEvents
| where timestamp > ago(14d) and name == "feature_exposed"
| project session_Id, flag = tostring(customDimensions.flag), state = tostring(customDimensions.state);
let done = customEvents
| where timestamp > ago(14d) and name == "request_submitted"
| distinct session_Id
| extend completed = 1;
exposed
| join kind=leftouter done on session_Id
| summarize sessions = dcount(session_Id), completed = dcountif(session_Id, completed == 1) by flag, state
| extend rate_pct = round(100.0 * completed / sessions, 1)
// 7. The weekly summary the flow writes (one row per metric)
let since = ago(7d);
union
 (customEvents | where timestamp > since and name == "sessionLoadSummary"
  | summarize value = percentile(todouble(customMeasurements.timeToAppInteractive), 75) | extend metric = "Open time p75 (ms)"),
 (exceptions | where timestamp > since | summarize value = todouble(count()) | extend metric = "Errors"),
 (pageViews | where timestamp > since | summarize value = todouble(dcount(session_Id)) | extend metric = "Sessions")
| project metric, value

Pin queries 2, 3 and 5 to a workbook. Add a log search alert on query 5, for example when errors in an hour pass a threshold you set from the first two weeks of data. DI Use Funnels for a fixed path, such as open, filter, submit, and User Flows to explore. D

23b.7

Bringing it back into the app

Telemetry pays off only when it changes the next release. Every step after the first uses tools a managed app already has. I

  1. A weekly flow reads Application Insights. A scheduled Power Automate flow owned by the service account runs query 7 with the Azure Monitor Logs action "Run query and list results". It then writes one row per metric into a SharePoint "Telemetry" list: week, metric, value, last week's value, status (ok, watch, act) and query name. The connector supports a service principal, and it allows 100 calls per connection every 60 seconds, so use one query, not one per metric. DI
  2. The app shows an Insights screen. An admin route reads the Telemetry list through the SharePoint connector, which is on the default allow-list (section 9.4). It shows this week against last week, with a status chip per metric. Restrict the list itself to the owners group. Hiding the route is not security. DI
  3. Cowork reviews it weekly. A Cowork scheduled prompt (you can keep up to 25 scheduled prompts) runs a few hours after the flow. It reads the Telemetry list and the feedback list, then proposes items. DI
  4. Findings become backlog items. Accepted items go into an "Improvements" list: title, evidence (metric and week), expected effect, effort, status, feature switch, owner and release. They also get a line in RELEASE-LOG.md. I
  5. Code ships the change dark and measures it. The next Code session picks up the top accepted item. Code ships it behind a switch, logs feature_exposed, and after two weeks query 6 decides whether the switch stays on. I

The app can't read Application Insights directly at run time. Azure Monitor Logs isn't on the managed-runtime default allow-list and is a premium connector. Calling the query API from the browser would need a token the app can't hold. So the list written by the flow is the only route we recommend. DI At build time, a local coding agent (section 22.6) can use the Azure MCP Server's Log Analytics query and metrics tools with your own Azure sign-in. D Whether Copilot Code can attach that server isn't documented. Exporting query results to CSV and attaching them to a Code session also works. I

Weekly app review. Read this week's rows in the "Telemetry" SharePoint list for <app name> and the last 7 days of the "Feedback" list. Compare each metric with last week and flag any metric with status watch or act, any error that affects more than a few sessions and any screen whose visits fell sharply. For each finding, propose one improvement with: title, evidence (metric, week, value), expected effect, rough effort and a feature switch name. Don't include anyone's name or feedback text that identifies a person. Draft the items for my approval; do not write to the Improvements list until I say yes.
Turn findings into a release. Read the top three items with status "accepted" in the "Improvements" SharePoint list. For each, write a short spec in specs/, build it behind a new feature switch that is off by default, add feature_exposed tracking for that switch using the track helper, and add tests. Update RELEASE-LOG.md with the item ids and the KQL query that will judge each change. Run the release checks, publish to preview and stop for my review before going live.

23b.8

Documented, observed and inferred

ClaimTag
Application Insights setup, setConfig logger, connect-src entry, telemetry only after loadD Managed-app monitoring and content security policy pages
Built-in session load and network request metricsD Managed-app and code-app pages
IP handling, sampling default, 90-day retention, free allowance, usage toolsD Azure Monitor pages and the Azure pricing page
Azure Monitor Logs is premium, supports a service principal, throttles per connectionD Connector reference
Settings-list storage, the weekly loop, the Insights screen, the sizing exampleI Our patterns, not yet run in production
A managed app using this setup in productionNot observed

23b.9

Troubleshooting

SymptomLikely causeFix
Logs stays empty; console shows "Refused to connect"Origin not in connect-srcSend the exception recipe with the exact origin
Logs stays empty; no console errorSettings row missing, Enabled false, or a malformed string (it must start with InstrumentationKey=)Check the list row and its permissions
Failed launches are missingTelemetry starts only after loadUse the admin center's monitoring
User counts look highAnonymous ids per browser and cookieTrust session counts more than user counts
Every screen has the same page nameRoute changes in a single-page appCall trackPageView with the screen name
URLs in metrics contain idsThe logger forwards raw URLsScrub them in the logger before trackEvent
Bill higher than expectedPolling or chatty calls, sampling at 100Lower sampling, set the daily cap, cache reads
The weekly flow fails with 429Connector throttlingOne union query per run
Insights screen is empty for an ownerFlow didn't run, or the list isn't shared with themCheck the flow run history and list permissions