Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 34 additions & 34 deletions .github/workflows/daily-vulnhunter-scan.lock.yml

Large diffs are not rendered by default.

55 changes: 14 additions & 41 deletions actions/setup/js/assign_agent_helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// @safe-outputs-exempt SEC-004 — body fields are read-only API context, never written back

const { getErrorMessage } = require("./error_helpers.cjs");
const { getPromptPath, renderTemplateFromFile } = require("./messages_core.cjs");

/**
* Shared helper functions for assigning coding agents (like Copilot) to issues.
Expand Down Expand Up @@ -429,54 +430,26 @@ async function assignAgentToIssue(
* @param {string} agentName - Agent name for error messages
*/
function logPermissionError(agentName) {
core.error(`Failed to assign ${agentName}: Insufficient permissions`);
core.error("");
core.error("Assigning Copilot coding agent requires the following token permissions:");
core.error(" Fine-grained PAT:");
core.error(" - Read access to metadata");
core.error(" - Read and write access to actions, contents, issues, and pull requests");
core.error(" Classic PAT:");
core.error(" - repo scope");
core.error("");
core.error(" Repository settings:");
core.error(" - Ensure assignee has access to the repository");
core.error("");
core.error(" Organization/Enterprise settings and Copilot policy:");
core.error(" - Check if your org restricts bot assignments");
core.error(" - Verify Copilot is enabled for your repository");
core.error("");
core.info("For more information, see: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api");
const errorTemplatePath = getPromptPath("copilot_assignment_permission_error.md");
const referencesTemplatePath = getPromptPath("copilot_assignment_permission_references.md");
const renderedError = renderTemplateFromFile(errorTemplatePath, { agent_name: agentName }).trimEnd();
const renderedReferences = renderTemplateFromFile(referencesTemplatePath, {}).trimEnd();

for (const line of renderedError.split("\n")) {
core.error(line);
}
for (const line of renderedReferences.split("\n")) {
core.info(line);
}
}

/**
* Generate permission error summary content for step summary
* @returns {string} Markdown content for permission error guidance
*/
function generatePermissionErrorSummary() {
return `
### ⚠️ Permission Requirements

Assigning Copilot coding agent requires a token with the correct permissions. See the [official GitHub Copilot cloud agent API documentation](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api) for details.

**Fine-grained personal access token** — requires these repository permissions:
- Read access to **metadata**
- Read and write access to **actions**, **contents**, **issues**, and **pull requests**

**Classic personal access token** — requires the **\`repo\`** scope.

**Token capability note:**
- Current token lacks permission for \`POST /repos/{owner}/{repo}/issues/{issue_number}/assignees\`.
- Token must be able to assign users to issues in the target repository.

**Recommended remediation paths:**
1. Use a fine-grained PAT with the permissions listed above, or a classic PAT with the \`repo\` scope.
2. Ensure repository settings allow assignee updates.
3. Verify Copilot coding agent is enabled for the repository and organization policy allows bot assignments.

**Why this failed:** The token could not update issue assignees via the REST API.

📖 Reference: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api
`;
const templatePath = getPromptPath("copilot_assignment_permission_requirements.md");
return renderTemplateFromFile(templatePath, {});
}

/**
Expand Down
29 changes: 25 additions & 4 deletions actions/setup/js/assign_agent_helpers.test.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { describe, it, expect, beforeEach, beforeAll, afterAll, vi } from "vitest";
import { syncRuntimePromptTemplates } from "./test_prompt_templates.js";

const { runtimePromptsDir } = syncRuntimePromptTemplates(import.meta.url);

// Mock the global objects that GitHub Actions provides
const mockCore = {
Expand Down Expand Up @@ -34,6 +37,20 @@ const { AGENT_LOGIN_NAMES, getAgentName, getAgentLogins, getAvailableAgentLogins
await import("./assign_agent_helpers.cjs");

describe("assign_agent_helpers.cjs", () => {
const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR;

beforeAll(() => {
process.env.GH_AW_PROMPTS_DIR = runtimePromptsDir;
});

afterAll(() => {
if (originalPromptsDir === undefined) {
delete process.env.GH_AW_PROMPTS_DIR;
return;
}
process.env.GH_AW_PROMPTS_DIR = originalPromptsDir;
});

beforeEach(() => {
vi.clearAllMocks();
});
Expand Down Expand Up @@ -437,18 +454,22 @@ describe("assign_agent_helpers.cjs", () => {
const result = await assignAgentToIssue("id", "agent", [], "copilot", null, null, null, null, null, restClient, taskContext);

expect(result).toBe(false);
expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Insufficient permissions"));
expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Copilot assignment permission requirements not met"));
expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("GH_AW_AGENT_TOKEN"));
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication"));
});
});

describe("generatePermissionErrorSummary", () => {
it("should return markdown content with permission requirements", () => {
const summary = generatePermissionErrorSummary();

expect(summary).toContain("### ⚠️ Permission Requirements");
expect(summary).toContain("Fine-grained personal access token");
expect(summary).toContain("### ⚠️ Copilot Assignment Permission Requirements");
expect(summary).toContain("GH_AW_AGENT_TOKEN");
expect(summary).toContain("GitHub App installation token");
expect(summary).toContain("actions**, **contents**, **issues**");
expect(summary).toContain("POST /repos/{owner}/{repo}/issues/{issue_number}/assignees");
expect(summary).toContain("https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication");
expect(summary).toContain("https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api");
});
});
Expand Down
17 changes: 7 additions & 10 deletions actions/setup/js/handle_agent_failure.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2184,27 +2184,24 @@ function buildAssignmentErrorsContext(assignmentErrors) {
return "";
}

let context = buildWarningAlertLine("Agent Assignment Failed", "Failed to assign agent to issues or pull requests.");
context += "\n**Assignment Errors:**\n";

const errorLines = assignmentErrors.split("\n").filter(line => line.trim());
let renderedErrors = "";
for (const errorLine of errorLines) {
const parts = errorLine.split(":");
if (parts.length >= 4) {
const type = parts[0]; // "issue" or "pr"
const number = parts[1];
const agent = parts[2];
const error = parts.slice(3).join(":");
context += `- ${type === "issue" ? "Issue" : "PR"} #${number} (agent: ${agent}): ${error}\n`;
renderedErrors += `- ${type === "issue" ? "Issue" : "PR"} #${number} (agent: ${agent}): ${error}\n`;
}
}

context += "\nTo resolve this, verify the agent token and Copilot access configuration:\n";
context += "- Configure a valid `GH_AW_AGENT_TOKEN` as a fine-grained PAT with **Agent tasks: read and write** permission (GitHub App installation tokens are not supported)\n";
context += "- Ensure Copilot coding agent is enabled for this repository and a Copilot Business or Enterprise subscription is active\n";
context += "- Docs: https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication\n\n";

return context;
const templatePath = getPromptPath("copilot_assignment_errors_context.md");
return renderTemplateFromFile(templatePath, {
warning_line: buildWarningAlertLine("Agent Assignment Failed", "Failed to assign agent to issues or pull requests."),
assignment_errors: renderedErrors.trimEnd(),
});
}
/**
* Build a context string when assigning the Copilot coding agent to created issues failed.
Expand Down
63 changes: 54 additions & 9 deletions actions/setup/js/handle_agent_failure.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { createRequire } from "module";
import { syncRuntimePromptTemplates } from "./test_prompt_templates.js";

const require = createRequire(import.meta.url);
const { runtimePromptsDir } = syncRuntimePromptTemplates(import.meta.url);

describe("handle_agent_failure", () => {
let main;
Expand All @@ -13,6 +15,7 @@ describe("handle_agent_failure", () => {
let buildFailureIssueTitle;
let buildSecretVerificationContext;
let buildAssignmentErrorsContext;
let buildAssignCopilotFailureContext;
let getActionFailureIssueExpiresHours;
const ENGINE_RATE_LIMIT_TEMPLATE = "> [!WARNING]\n> **Engine Rate Limited (HTTP 429)**\n> OTLP telemetry\n> {engine_label}\n";
const ENGINE_MAX_RUNS_EXCEEDED_TEMPLATE = "> [!WARNING]\n> **Engine Max Runs Exceeded**\n> max-runs guardrail\n> {engine_label}\n";
Expand Down Expand Up @@ -40,6 +43,7 @@ describe("handle_agent_failure", () => {
buildFailureIssueTitle,
buildSecretVerificationContext,
buildAssignmentErrorsContext,
buildAssignCopilotFailureContext,
getActionFailureIssueExpiresHours,
} = require("./handle_agent_failure.cjs"));
});
Expand Down Expand Up @@ -1372,15 +1376,56 @@ describe("handle_agent_failure", () => {
});

it("renders assignment failures with token guidance docs", () => {
const result = buildAssignmentErrorsContext("issue:42:copilot:Bad credentials\npr:7:copilot:copilot coding agent is not available for this repository");

expect(result).toContain("Agent Assignment Failed");
expect(result).toContain("Issue #42 (agent: copilot): Bad credentials");
expect(result).toContain("PR #7 (agent: copilot): copilot coding agent is not available for this repository");
expect(result).toContain("GH_AW_AGENT_TOKEN");
expect(result).toContain("Agent tasks: read and write");
expect(result).not.toContain("copilot-requests: write");
expect(result).toContain("https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication");
const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR;
try {
process.env.GH_AW_PROMPTS_DIR = runtimePromptsDir;
const result = buildAssignmentErrorsContext("issue:42:copilot:Bad credentials\npr:7:copilot:copilot coding agent is not available for this repository");

expect(result).toContain("Agent Assignment Failed");
expect(result).toContain("Issue #42 (agent: copilot): Bad credentials");
expect(result).toContain("PR #7 (agent: copilot): copilot coding agent is not available for this repository");
expect(result).toContain("GH_AW_AGENT_TOKEN");
expect(result).toContain("metadata: read");
expect(result).toContain("GitHub App installation token");
expect(result).toContain("https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication");
expect(result).toContain("https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api");
expect(result).not.toContain("copilot-requests: write");
} finally {
if (originalPromptsDir === undefined) {
delete process.env.GH_AW_PROMPTS_DIR;
} else {
process.env.GH_AW_PROMPTS_DIR = originalPromptsDir;
}
}
});
});

describe("buildAssignCopilotFailureContext", () => {
it("returns empty string when there are no copilot assignment failures", () => {
expect(buildAssignCopilotFailureContext(false, "")).toBe("");
});

it("renders standardized copilot assignment remediation guidance", () => {
const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR;
try {
process.env.GH_AW_PROMPTS_DIR = runtimePromptsDir;
const result = buildAssignCopilotFailureContext(true, "issue:42:copilot:Bad credentials");

expect(result).toContain("Copilot Assignment Failed");
expect(result).toContain("Issue #42: Bad credentials");
expect(result).toContain("GH_AW_AGENT_TOKEN");
expect(result).toContain("metadata");
expect(result).toContain("GitHub App installation token");
expect(result).toContain("YOUR_AGENT_PAT");
expect(result).toContain("https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication");
expect(result).toContain("https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api");
} finally {
if (originalPromptsDir === undefined) {
delete process.env.GH_AW_PROMPTS_DIR;
} else {
process.env.GH_AW_PROMPTS_DIR = originalPromptsDir;
}
}
});
});

Expand Down
10 changes: 10 additions & 0 deletions actions/setup/js/test_prompt_templates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { cpSync, mkdirSync } from "fs";
import path from "path";

export function syncRuntimePromptTemplates(metaUrl) {
const promptsDir = new URL("../md", metaUrl).pathname;
const runtimePromptsDir = path.join(process.env.RUNNER_TEMP || "/tmp", "gh-aw", "prompts");
mkdirSync(runtimePromptsDir, { recursive: true });
cpSync(promptsDir, runtimePromptsDir, { recursive: true });
return { promptsDir, runtimePromptsDir };
}
22 changes: 10 additions & 12 deletions actions/setup/md/assign_copilot_to_created_issues_failure.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@

**Copilot Assignment Failed**: The workflow created an issue but could not assign the Copilot coding agent to it. This typically happens when:

- The `GH_AW_AGENT_TOKEN` secret is missing or has expired
- The token does not have the `issues: write` permission
- The Copilot coding agent is not available for this repository
- GitHub API credentials are invalid (`Bad credentials`)
**Copilot Assignment Failed**: The workflow created an issue but could not assign the Copilot coding agent because the workflow token could not update issue assignees.

**Failed assignments:**
{issues}

To resolve this, verify that:
1. The `GH_AW_AGENT_TOKEN` secret is configured in your repository settings
2. The token belongs to an account with an active Copilot subscription
3. The token has `issues: write` permission for this repository
To resolve this:
1. Set `GH_AW_AGENT_TOKEN` to a fine-grained PAT with read access to `metadata` and read/write access to `actions`, `contents`, `issues`, and `pull requests`, or use a classic PAT with the `repo` scope.
2. Do not use a GitHub App installation token for Copilot assignment; the API rejects it.
3. Ensure the token owner can access the repository and assign users to issues.
4. Verify Copilot coding agent is enabled for this repository and organization policy allows bot assignments.

```bash
gh aw secrets set GH_AW_AGENT_TOKEN --value "YOUR_TOKEN"
gh aw secrets set GH_AW_AGENT_TOKEN --value "YOUR_AGENT_PAT"
```

See: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/auth.mdx
See:
- https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication
- https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api
11 changes: 11 additions & 0 deletions actions/setup/md/copilot_assignment_errors_context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{warning_line}
**Assignment Errors:**
{assignment_errors}

To resolve this, verify the token and Copilot assignment configuration:
- Set `GH_AW_AGENT_TOKEN` to a fine-grained PAT with **metadata: read** and **actions**, **contents**, **issues**, and **pull requests: write**, or use a classic PAT with `repo`
- Do not use a GitHub App installation token for Copilot assignment; the API rejects it
- Ensure the token owner can access the repository and assign users to issues
- Verify Copilot coding agent is enabled for this repository and organization policy allows bot assignments
- Docs: https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication
- API reference: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api
14 changes: 14 additions & 0 deletions actions/setup/md/copilot_assignment_permission_error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Failed to assign {agent_name}: Copilot assignment permission requirements not met

Copilot assignment needs a Personal Access Token (PAT) that can update issue assignees:
Fine-grained PAT:
- Read access to metadata
- Read and write access to actions, contents, issues, and pull requests
Classic PAT:
- repo scope

Remediation:
1. Set GH_AW_AGENT_TOKEN to a PAT with the permissions above
2. GitHub App installation tokens are not supported for Copilot assignment
3. Ensure the token owner can access the repository and assign users to issues
4. Verify Copilot coding agent is enabled and org policy allows bot assignments
2 changes: 2 additions & 0 deletions actions/setup/md/copilot_assignment_permission_references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gh-aw docs: https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication
GitHub docs: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api
17 changes: 17 additions & 0 deletions actions/setup/md/copilot_assignment_permission_requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### ⚠️ Copilot Assignment Permission Requirements

Copilot assignment failed because the workflow token could not update issue assignees via `POST /repos/{owner}/{repo}/issues/{issue_number}/assignees`.

**Required token options**
- **Fine-grained personal access token** — Read access to **metadata** and read/write access to **actions**, **contents**, **issues**, and **pull requests**
- **Classic personal access token****`repo`** scope

**Remediation**
- Set `GH_AW_AGENT_TOKEN` to a PAT with one of the permission sets above.
- Do not use a GitHub App installation token for Copilot assignment; the API rejects it.
- Ensure the token owner can access the repository and assign users to issues.
- Verify Copilot coding agent is enabled for the repository and organization policy allows bot assignments.

**References**
- [gh-aw Copilot Cloud Agent authentication](https://github.github.com/gh-aw/reference/copilot-cloud-agent/#authentication)
- [Official GitHub Copilot cloud agent API documentation](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api#using-the-issues-api)
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/copilot-cloud-agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ The required token type and permissions depend on whether you own the repository
### Using a GitHub App

:::caution[GitHub App tokens are not supported for Copilot assignment]
The Copilot assignment API only accepts fine-grained PATs — GitHub App installation tokens are rejected regardless of permissions. When `github-app:` is configured in `safe-outputs`, `assign-to-agent` falls back to: explicit `github-token:` in `assign-to-agent`, then `github-token:` at the `safe-outputs` level, then the magic secret chain (`GH_AW_AGENT_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN`).
The Copilot assignment API requires a Personal Access Token (fine-grained PAT with the permissions above, or a classic PAT with `repo`) — GitHub App installation tokens are rejected regardless of permissions. When `github-app:` is configured in `safe-outputs`, `assign-to-agent` falls back to: explicit `github-token:` in `assign-to-agent`, then `github-token:` at the `safe-outputs` level, then the magic secret chain (`GH_AW_AGENT_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN`).
:::

### Using a magic secret
Expand Down
5 changes: 3 additions & 2 deletions pkg/cli/workflows/test-assign-to-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ permissions:
pull-requests: read

# NOTE: Assigning Copilot coding agent requires:
# 1. A Personal Access Token (PAT) or GitHub App token with repo scope
# 1. A Personal Access Token (PAT)
# - The standard GITHUB_TOKEN does NOT have permission to assign bot agents
# - GitHub App installation tokens are NOT supported for Copilot assignment
# - Create a PAT at: https://github.com/settings/tokens
# - Add it as a repository secret named GH_AW_AGENT_TOKEN
# - Required scopes: repo (full control) or fine-grained: actions, contents, issues, pull-requests (write)
# - Required scopes: repo (classic PAT) or fine-grained: metadata (read) plus actions, contents, issues, pull-requests (write)
#
# 2. All four workflow permissions declared above (for the safe output job)
#
Expand Down
Loading