{"meta":{"title":"Cloud sessions","intro":"Cloud sessions run Copilot work on GitHub-hosted compute and appear in the agents panel on GitHub. Use them when your app should create a session that executes remotely instead of starting a local GitHub Copilot CLI session on the user's machine or your server.","product":"GitHub Copilot","breadcrumbs":[{"href":"/en/copilot","title":"GitHub Copilot"},{"href":"/en/copilot/how-tos","title":"How-tos"},{"href":"/en/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/en/copilot/how-tos/copilot-sdk/features","title":"Features"},{"href":"/en/copilot/how-tos/copilot-sdk/features/cloud-sessions","title":"Cloud Sessions"}],"documentType":"article"},"body":"# Cloud sessions\n\nCloud sessions run Copilot work on GitHub-hosted compute and appear in the agents panel on GitHub. Use them when your app should create a session that executes remotely instead of starting a local GitHub Copilot CLI session on the user's machine or your server.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n## Prerequisites\n\nBefore creating a cloud session, make sure:\n\n* The user has Copilot access with cloud-agent entitlement.\n* The session can authenticate to GitHub, either with a user token or a logged-in Copilot CLI identity.\n* You can associate the session with a GitHub repository. This is optional in the SDK type, but recommended so the cloud agent has the correct repository context.\n* Organization policies allow remote control and viewing sessions from cloud surfaces.\n\n## Creating a cloud session\n\nSet the create-session `cloud` option to create a cloud session. You can include repository metadata to associate the cloud session with a GitHub repository.\n\n<!-- tabs:start -->\n\n### TypeScript\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nawait client.start();\n\nconst session = await client.createSession({\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n  cloud: {\n    repository: {\n      owner: \"github\",\n      name: \"copilot-sdk\",\n      branch: \"main\",\n    },\n  },\n});\n```\n\n### Python\n\n```python\nfrom copilot import (\n    CloudSessionOptions,\n    CloudSessionRepository,\n    CopilotClient,\n    PermissionHandler,\n)\n\nclient = CopilotClient()\nawait client.start()\n\nsession = await client.create_session(\n    on_permission_request=PermissionHandler.approve_all,\n    cloud=CloudSessionOptions(\n        repository=CloudSessionRepository(\n            owner=\"github\",\n            name=\"copilot-sdk\",\n            branch=\"main\",\n        )\n    ),\n)\n```\n\n### Go\n\n```golang\nclient := copilot.NewClient(nil)\nif err := client.Start(ctx); err != nil {\n    return err\n}\n\nsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n    Cloud: &copilot.CloudSessionOptions{\n        Repository: &copilot.CloudSessionRepository{\n            Owner:  \"github\",\n            Name:   \"copilot-sdk\",\n            Branch: \"main\",\n        },\n    },\n    OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {\n        return &rpc.PermissionDecisionApproveOnce{}, nil\n    },\n})\n_ = session\n```\n\n### .NET\n\n```csharp\nawait using var client = new CopilotClient();\n\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    Cloud = new CloudSessionOptions\n    {\n        Repository = new CloudSessionRepository\n        {\n            Owner = \"github\",\n            Name = \"copilot-sdk\",\n            Branch = \"main\",\n        },\n    },\n    OnPermissionRequest = (req, inv) =>\n        Task.FromResult(PermissionDecision.ApproveOnce()),\n});\n```\n\n### Java\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\ntry (var client = new CopilotClient()) {\n    client.start().get();\n\n    var session = client.createSession(\n        new SessionConfig()\n            .setCloud(new CloudSessionOptions()\n                .setRepository(new CloudSessionRepository()\n                    .setOwner(\"github\")\n                    .setName(\"copilot-sdk\")\n                    .setBranch(\"main\")))\n            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n    ).get();\n}\n```\n\n### Rust\n\n```rust\nuse std::sync::Arc;\nuse github_copilot_sdk::{CloudSessionOptions, CloudSessionRepository, SessionConfig};\nuse github_copilot_sdk::handler::ApproveAllHandler;\n\nlet session = client.create_session(\n    SessionConfig::default()\n        .with_cloud(CloudSessionOptions::with_repository(\n            CloudSessionRepository::new(\"github\", \"copilot-sdk\").with_branch(\"main\"),\n        ))\n        .with_permission_handler(Arc::new(ApproveAllHandler)),\n).await?;\n```\n\n<!-- tabs:end -->\n\n## Sending the first prompt\n\nCloud sessions initialize in two phases: `createSession` resolves as soon as the agent has reserved the task, but the remote `copilot-agent` worker takes another second or two to connect and emit `session.start`. If you call `session.send` before that, the runtime's `RemoteSession.send` throws `\"Remote session is still starting\"`, but the schema wrapper is fire-and-forget and **silently swallows the error** while still returning a fresh `messageId` to your code. The prompt is dropped on the server and never reaches the worker.\n\nTo send reliably, subscribe to events **before** sending and await the first `session.start` event whose `producer` is `\"copilot-agent\"`:\n\n<!-- docs-validate: skip -->\n\n```typescript\nimport { CopilotClient, type CopilotSession } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nawait client.start();\n\nconst session: CopilotSession = await client.createSession({\n  streaming: true, // required for assistant.message_delta to fire\n  cloud: { repository: { owner: \"github\", name: \"copilot-sdk\" } },\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n\n// Subscribe BEFORE sending so you don't miss the start event.\nconst ready = new Promise<void>((resolve) => {\n  const off = session.on(\"session.start\", (event) => {\n    if (event.data?.producer === \"copilot-agent\") {\n      off();\n      resolve();\n    }\n  });\n});\n\nawait ready;\nawait session.send({ prompt: \"Summarize the README\" });\n```\n\nA few notes:\n\n* Set `streaming: true` on `createSession` so the runtime emits `assistant.message_delta` events. Without it, the only assistant signal you get is the final `assistant.message` — fine for batch use, but the chat will look frozen if you're rendering a live UI. See [Streaming session events](/en/copilot/how-tos/copilot-sdk/features/streaming-events).\n* Only the **first** `session.send` is sensitive to this race. Subsequent sends on the same session work normally because the runtime keeps `hasSessionStarted` set for the life of the session.\n* Apply a timeout (e.g. 60 s) around the `ready` promise so a stuck session doesn't hang your app forever.\n* The same pattern works in every SDK language — subscribe to `session.start`, check `producer === \"copilot-agent\"`, then call `send`.\n\n## Accessing the agent session URL\n\nCloud sessions are inherently remote: once the worker connects, the session is published to `https://github.com/copilot/tasks/{sessionId}` and the runtime emits a `session.info` event with the URL. You do **not** need to call `remote.enable()`— that API is only for syncing a local session to GitHub.\n\nCapture the URL by subscribing to `session.info` and filtering by `infoType: \"remote\"`:\n\n<!-- docs-validate: skip -->\n\n```typescript\nsession.on(\"session.info\", (event) => {\n  if (event.data?.infoType === \"remote\" && event.data.url) {\n    console.log(\"Open from web or mobile:\", event.data.url);\n    // For example, surface in your UI as a shareable link or QR code.\n  }\n});\n```\n\nThe event fires shortly after `session.start`. If your renderer mounts after the event has already fired, persist the URL alongside the session record in your app's state and rehydrate on remount — the runtime does not re-emit `session.info` on its own.\n\nFor the same wiring on local sessions promoted via `remote: true`, see [Remote sessions](/en/copilot/how-tos/copilot-sdk/features/remote-sessions).\n\n## Repository association\n\nThe `cloud.repository` object associates the cloud session with a GitHub repository:\n\n| Field    | Required | Description                                                                                                               |\n| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |\n| `owner`  | Yes      | Repository owner or organization.                                                                                         |\n| `name`   | Yes      | Repository name.                                                                                                          |\n| `branch` | No       | Branch to use for repository context. Omit it to let the runtime choose the default branch or current repository context. |\n\nRepository association is optional in the SDK type, but include it whenever your app knows the target repository. It helps the session appear with the right repository context in the agents panel and gives the cloud agent a clearer starting point.\n\nUse `branch` when the work should start from a specific branch. If your app is creating sessions from pull requests, issue triage flows, or deployment workflows, pass the branch that matches the user-visible task.\n\n## Resuming a cloud session\n\nThe `cloud` option only applies when creating a new session. To resume an existing cloud session, use the standard resume API for the SDK language:\n\n```typescript\nconst session = await client.resumeSession(\"session-id\", {\n  onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\nDo not pass `cloud` again on resume. The saved session metadata determines that the session is cloud-backed, and resume follows the normal session resume path.\n\n## Org policies and entitlements\n\nCloud session creation can fail when the user or organization is not entitled to cloud-agent execution or when organization-level policies block the flow. In particular, policies for cloud sandbox can prevent clients from creating the cloud task.\n\nWhen this happens, the runtime reports a `\"policy_blocked\"` failure reason for cloud task creation. Treat this as an authorization or policy outcome, not as a transient infrastructure failure.\n\nIn TypeScript, check for the reason before retrying:\n\n```typescript\ntry {\n  await client.createSession({ cloud: { repository } });\n} catch (error) {\n  if ((error as { reason?: string }).reason === \"policy_blocked\") {\n    // Show an admin-facing message or link to org policy settings.\n  }\n  throw error;\n}\n```\n\nIn languages where SDK errors are represented differently, inspect the surfaced error reason or code and handle `\"policy_blocked\"` explicitly. Retrying without a policy change is not expected to succeed.\n\n## Integration ID and routing\n\nCloud sessions are stamped with a `Copilot-Integration-Id` header derived from the `GITHUB_COPILOT_INTEGRATION_ID` environment variable. This integration ID is used for routing, attribution, and integration-specific behavior.\n\nFor multi-user server guidance and full integration ID details, see [Multi-tenancy and server deployments](/en/copilot/how-tos/copilot-sdk/setup/multi-tenancy).\n\nSDK-created cloud sessions are routed to the `copilot-developer-sandbox` agent slug. The name is an internal routing slug for the cloud agent and does not mean the session uses the local Windows sandbox.\n\n## Advanced: `COPILOT_MC_BASE_URL`\n\nBy default, the runtime derives the agent session base URL from the configured Copilot API URL. Set `COPILOT_MC_BASE_URL` only when you need to override that session endpoint.\n\nThis may be required for GitHub Enterprise Server deployments. Confirm the correct value and support status with your GitHub representative before relying on it in production.\n\n```shell\nCOPILOT_MC_BASE_URL=\"https://example.com/agents\"\n```\n\n## Cloud sessions vs. remote sessions\n\n| Capability               | Remote sessions                             | Cloud sessions                        |\n| ------------------------ | ------------------------------------------- | ------------------------------------- |\n| Execution location       | Local machine or your server                | GitHub-hosted compute                 |\n| Session role             | Shares a local session to GitHub web/mobile | Creates and routes the hosted session |\n| SDK option               | `remote: true` on the client or session     | `cloud: { ... }` on create session    |\n| Resume path              | Standard resume                             | Standard resume                       |\n| Windows sandbox relation | Unrelated                                   | Unrelated                             |\n\nUse remote sessions when the session should execute where the SDK runtime is already running, but also be accessible from the agents panel on GitHub. Use cloud sessions when the session should execute on GitHub-hosted compute.\n\n## Troubleshooting\n\n| Symptom                                                                                                              | Likely cause                                                                                             | What to check                                                                                                                                       |\n| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Cloud session creation returns `\"policy_blocked\"`                                                                    | Organization policy blocks remote control or view from cloud flows                                       | Check org Copilot policies and user entitlement                                                                                                     |\n| Session creates without repository context                                                                           | `cloud.repository` was omitted                                                                           | Pass `owner`, `name`, and optionally `branch`                                                                                                       |\n| Resume ignores a new `cloud` option                                                                                  | `cloud` only applies to new sessions                                                                     | Resume the existing session normally                                                                                                                |\n| Confusion with sandbox settings                                                                                      | Windows sandbox and cloud sessions are separate                                                          | Do not use `SANDBOX=true` for cloud execution                                                                                                       |\n| `session.send` resolves with a `messageId` but no `assistant.*` events fire and no prompt appears in the session log | The session.send raced ahead of `session.start` from the remote worker; the runtime swallowed the prompt | Await the first `session.start` event with `producer === \"copilot-agent\"` before sending. See [Sending the first prompt](#sending-the-first-prompt) |\n| Live UI never updates even though the cloud worker is processing                                                     | `streaming` was not set on `createSession`, so only the final `assistant.message` is emitted             | Set `streaming: true` on `createSession` and re-launch                                                                                              |\n| Cloud session works but no shareable URL appears in your UI                                                          | App never subscribed to `session.info` for the URL                                                       | Subscribe to `session.info` and filter `infoType === \"remote\"`. See [Accessing the agent session URL](#accessing-the-agent-session-url)             |\n\n## See also\n\n* [Remote sessions](/en/copilot/how-tos/copilot-sdk/features/remote-sessions): share locally hosted sessions to the agents panel on GitHub\n* [Streaming session events](/en/copilot/how-tos/copilot-sdk/features/streaming-events): subscribe to `assistant.*` deltas for live UI rendering\n* [Multi-tenancy and server deployments](/en/copilot/how-tos/copilot-sdk/setup/multi-tenancy): integration IDs and server deployment patterns\n* [Authentication](/en/copilot/how-tos/copilot-sdk/auth): configure GitHub authentication for SDK sessions"}