-
Notifications
You must be signed in to change notification settings - Fork 281
Description
For discussion : solution direction for automatic user attribution for Copilot coding agent sessions in reusable workflows
Summary
I would like to discuss a mechanism for engine: copilot (gh-aw) workflows to attribute Copilot coding agent premium requests to the user who triggered the workflow — without requiring a user-specific COPILOT_GITHUB_TOKEN PAT to be stored as a repository secret.
One possible approach I've explored is leveraging the OIDC actor / actor_id claims already present in the GitHub Actions runtime. I describe this in detail below as a starting point for discussion, while recognising that GitHub may have better approaches to solve this problem. This solution would also require changes beyond gh-aw, but it feels like this is the right repo to start the discussion from.
Context: platform-orchestrated coding agent sessions
An internal developer platform that provides standardised coding agent workflows to application teams across the organisation. The platform is hosted in a central repository and exposes reusable workflows that consumer teams invoke via workflow_call with secrets: inherit.
The flow:
- A developer posts a slash command on an issue in their team's repository.
- A thin caller workflow in the consumer repo triggers a reusable workflow in the platform repo via
workflow_call. - The reusable workflow runs an
engine: copilot(gh-aw) coding agent session — the agent reads the issue, writes code, and creates a pull request. - The developer reviews and merges.
Multiple teams across the organisation use the same platform workflows.
The problem: cost attribution for platform-orchestrated sessions
When a developer uses Copilot directly — assigning @copilot to an issue, using Copilot Chat, or accepting completions — premium requests are automatically attributed to that developer. No configuration is needed.
Platform-orchestrated sessions break this model. The engine: copilot runtime authenticates the coding agent session using COPILOT_GITHUB_TOKEN, a secret that must be present in the repository where the workflow executes. This token is a user-specific PAT — premium requests are attributed to whoever owns the token, not to the developer who triggered the run.
In a reusable workflow (workflow_call) scenario:
- If the consumer repo provides the token via
secrets: inherit, the session bills to one team member whose PAT is stored as the secret — not the triggering user. - If the platform repo provides the token, all sessions across all teams bill to a single platform account.
Neither option provides accurate per-user attribution.
How the current authentication chain works
The chain from a gh-aw workflow to a billed Copilot session involves three distinct systems:
gh-aw (compiler + runtime) → Copilot CLI → GitHub Copilot API (backend)
| Layer | What it does with COPILOT_GITHUB_TOKEN |
|---|---|
| gh-aw compiler | Emits the validate_multi_secret.sh step into the compiled .lock.yml. Hard-fails if the secret is absent. |
| gh-aw runtime | Passes COPILOT_GITHUB_TOKEN to the Copilot CLI as the auth credential when invoking engine: copilot. |
| Copilot CLI | Uses the token as a Bearer token to authenticate against the GitHub API (api.github.com/copilot/...). |
| Copilot API backend | Validates the token, resolves the token owner's user identity, checks Copilot license + premium request quota, and attributes billing to that user. |
The billing decision — who pays — is made at the last layer (Copilot API backend), based on token ownership. Every layer upstream simply passes the token through. This means any solution will require changes across multiple layers of this chain.
Current workaround (functional but not ideal)
Our current approach requires each consumer team to store a COPILOT_GITHUB_TOKEN in their repository secrets. This is a user-specific fine-grained PAT with the Copilot Requests permission, passed to the platform workflow via secrets: inherit.
# Consumer repo: .github/workflows/platform-caller.yml
name: Platform Agent
on:
issue_comment:
types: [created]
jobs:
agent:
if: contains(github.event.comment.body, '/run-agent')
uses: org/platform-repo/.github/workflows/agent.lock.yml@main
secrets: inheritWhy this is not ideal
| Concern | Detail |
|---|---|
| Org policy friction | Many enterprise organisations restrict PAT creation. Requiring every consumer team to create and manage a PAT conflicts with these policies. |
| Attribution is per-team, not per-user | One team member's PAT absorbs all sessions for the entire team. GitHub's billing reports show that person as the consumer, not the actual triggering developer. |
| Active rotation required | Teams must rotate the PAT not only when someone leaves, but also when that person's premium request allowance is exhausted while other team members still have capacity. |
| Alternative: service accounts | Dedicated service accounts per team eliminate key-person risk, but require an additional Copilot license for every consuming team plus governance overhead to provision and maintain them. |
None of these concerns exist for normal @copilot usage, where GitHub attributes sessions to the triggering user automatically.
What we're aiming for
A way for engine: copilot sessions to automatically attribute premium requests to the user who triggered the workflow, without requiring that user's PAT to be stored as a repository secret. The desired end state:
- Per-user attribution — identical to how native
@copilotusage is billed today. - Zero secret management — no PATs, no service accounts, no rotation.
- No consumer repo configuration — beyond the caller workflow file itself.
We don't prescribe a specific implementation. GitHub may have approaches we haven't considered. Below we sketch one possible solution based on OIDC as a starting point for discussion.
One possible approach: OIDC-based user attribution
The insight
The GitHub Actions OIDC provider (token.actions.githubusercontent.com) already issues tokens that contain the triggering user's identity:
{
"actor": "octocat",
"actor_id": "583231",
"repository": "org/consumer-repo",
"repository_id": "123456789",
"job_workflow_ref": "org/platform-repo/.github/workflows/agent.lock.yml@refs/heads/main",
...
}The actor and actor_id claims identify the developer who triggered the workflow — the person who posted the slash command. This information is already available to the GitHub Actions runtime at the point where the engine: copilot session is initiated.
How this could work
Allow the engine: copilot runtime to attribute the coding agent session's premium requests to the user identified by actor / actor_id, instead of requiring a user-specific COPILOT_GITHUB_TOKEN.
Conceptually, the flow could look like:
sequenceDiagram
participant Dev as Developer (actor)
participant Consumer as Consumer Repo
participant Platform as Platform Workflow (workflow_call)
participant Runtime as engine: copilot runtime
participant OIDC as GitHub OIDC Provider
participant CLI as Copilot CLI
participant API as Copilot API Backend
Dev->>Consumer: Posts slash command on issue
Consumer->>Platform: workflow_call (secrets: inherit)
Platform->>Runtime: engine: copilot step begins
Runtime->>OIDC: Request OIDC token (id-token: write)
OIDC-->>Runtime: JWT with actor=octocat, actor_id=583231
Runtime->>CLI: Invoke with OIDC token (no COPILOT_GITHUB_TOKEN)
CLI->>API: Initiate coding session (present OIDC token as user attestation)
API->>API: Verify: actor has Copilot license? Verify: actor has premium request capacity?
API-->>CLI: Session authorised, attributed to octocat
CLI-->>Runtime: Session complete
Runtime->>Consumer: Creates PR (attributed to octocat)
What this approach would touch
Based on our understanding of the authentication chain, this approach would require changes at multiple layers. The GitHub Actions OIDC provider already emits the required claims — no changes needed there.
| Component | Change required |
|---|---|
| gh-aw compiler | Stop hard-failing when COPILOT_GITHUB_TOKEN is absent. Support an OIDC fallback mode: when the secret is missing but permissions: id-token: write is set, compile the workflow to request an OIDC token instead. |
| gh-aw runtime | When in OIDC fallback mode, request an OIDC token from the Actions runtime and pass it (or the actor/actor_id claims) to the Copilot CLI via a new flag or env var. |
| Copilot CLI | Accept an OIDC JWT as an alternative authentication path (e.g., --oidc-token flag or COPILOT_OIDC_TOKEN env var). Present the token to the Copilot API backend for session initiation. |
| Copilot API backend | Accept an OIDC-attested identity as a valid billing source. Validate the JWT signature (issuer: token.actions.githubusercontent.com), extract actor_id, verify the actor has an active Copilot license and premium request capacity, and attribute the session to that user. |
| GitHub Actions OIDC provider | No change needed. Already emits actor, actor_id, and all required claims. |
What would NOT need to change
- Consumer repos: No secrets, no PATs, no service accounts. The triggering user's identity is carried by the OIDC token automatically.
- Platform workflows: No changes to workflow logic. The
engine: copilotfrontmatter remains the same. - User consent model: The user already consented to the workflow running by posting the slash command. The OIDC token is an attestation of who they are, not a delegation of credentials.
Security considerations for the OIDC approach
| Concern | Mitigation |
|---|---|
| Can a workflow impersonate a different user? | No. The OIDC actor / actor_id claims are set by the GitHub Actions runtime based on the actual event trigger. They cannot be overridden by workflow code. |
| Can a malicious workflow drain a user's premium requests? | The user must have write access to the repository and must have explicitly triggered the workflow (e.g., posted a comment). This is the same trust model as native @copilot usage: the user initiates the action. |
| Does this expose the user's credentials? | No. The OIDC token is an attestation of identity, not a credential. It cannot be used to access the user's repositories, secrets, or other resources. It only asserts "this workflow was triggered by user X." |
| What if the actor doesn't have a Copilot license? | The Copilot API should reject the session with a clear error, just as it does today when COPILOT_GITHUB_TOKEN belongs to a user without a license. |
workflow_call chains |
In a workflow_call chain, github.actor reflects the original triggering user, not the workflow author. The OIDC actor claim follows the same semantics. |
| OIDC token trust boundary | The Copilot API backend would only need to trust tokens from token.actions.githubusercontent.com — GitHub's own OIDC provider. This is the same trust model used by Azure, AWS, and GCP for workload identity federation. |
Why this matters at scale
| Aspect | Current (PAT-based) | Desired end state |
|---|---|---|
| Attribution granularity | Per-team (one PAT per repo) | Per-user (automatic, identical to native @copilot) |
| Consumer onboarding | Create PAT, store as secret, add caller workflow | Add caller workflow only (no secrets) |
| Token rotation | Active management — person leaves, quota exhausted, org policy changes | None — no tokens to manage |
| Service account overhead | Additional Copilot license per team + provisioning | None |
| Org policy compatibility | May conflict with PAT restrictions | No PATs required |
| Billing accuracy | One person absorbs entire team's usage | Actual user is billed, identical to all other Copilot features |
Alternatives we considered (and why they don't work)
| Alternative | Why it doesn't solve the problem |
|---|---|
GITHUB_TOKEN |
Installation access token (ghs_), no user identity, no Copilot entitlement. |
GitHub App user-to-server token (ghu_) |
Requires interactive OAuth consent flow — cannot be obtained in a headless Actions workflow. |
github.actor context variable |
Username string only, not a credential or attestation. Cannot be cryptographically verified by the Copilot API. |
| Custom cost attribution dashboard | Informational only — no billing separation, no per-user caps, duplicates what GitHub billing already provides. |
Next steps
We'd welcome a conversation to validate this problem statement and explore feasible solutions — whether OIDC-based or otherwise. We are happy to participate in a preview programme and provide feedback from our production platform.
Impact
We believe the use case of centralised agent platforms calling engine: copilot via workflow_call will become increasingly common as organisations adopt agentic workflows. Solving the attribution problem now prevents every platform team from independently building workarounds around PAT management and token rotation.
This feature would make cost attribution for platform-orchestrated Copilot sessions identical to native @copilot usage — per-user, automatic, and zero-configuration.