diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index b31b37834f0..c0150c28b18 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"214d9691823ffbf327cae3541a2809e9e03d844c3e69ad8a206db199aac42b94","body_hash":"a3bf9a70bb50b41de2a9702fe982e186a89322f435a5a1633ab73ac76758e592","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.2"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"214d9691823ffbf327cae3541a2809e9e03d844c3e69ad8a206db199aac42b94","body_hash":"bfda3055b12ee8a51b62d5da483fe2adead74c59e3c8db50292dbe457f7cc7bd","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.2"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.10","digest":"sha256:e47878fa4953f5b4d38b4ec12c155aa12ab9befea299ea2d21a8b104de8bcbc8","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.10@sha256:e47878fa4953f5b4d38b4ec12c155aa12ab9befea299ea2d21a8b104de8bcbc8"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.10","digest":"sha256:4bd2598466928efbd360fd6575b68c6b420a7ec3b7c1be20844c560a0dd2878e","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.10@sha256:4bd2598466928efbd360fd6575b68c6b420a7ec3b7c1be20844c560a0dd2878e"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.10"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.10","digest":"sha256:4d7a79482c47f2390f9fa87663cd9cb728bfb2380d9a9610479fa234c906ea98","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.10@sha256:4d7a79482c47f2390f9fa87663cd9cb728bfb2380d9a9610479fa234c906ea98"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:4d0101d8740c99b755181d19dc0067ac7eb40433d1c354fd715358bee4a296c1","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:4d0101d8740c99b755181d19dc0067ac7eb40433d1c354fd715358bee4a296c1"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 9ee1d12536d..eb37461b6fb 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -596,6 +596,8 @@ safeoutputs/assign_to_agent(issue_number=, agent="copilot") Use the exact field name `issue_number` (underscore). Do **not** use `issue-number` (hyphen), which is invalid and will fail safe-output validation. +**Important**: Only call `assign_to_agent` for **issues**, never for pull requests. The pre-fetched list already contains only issues, so never pass a PR number here. If you are ever unsure whether a number refers to an issue or a PR, call `issue_read` with `method: get` and check: if the response includes a `pull_request` URL field, skip that item. + Do not use GitHub tools for this assignment. The `assign_to_agent` tool will handle the actual assignment. The Copilot coding agent will: @@ -641,6 +643,7 @@ Issue Monster runs frequently (every 30 minutes), so keeping each run lean is cr - ✅ **Always report outcome**: If no issues are assigned, use the `noop` tool to explain why - ✅ **Skip integrity-blocked issues**: If `issue_read` is blocked by integrity policy, skip that issue and continue — never call `missing_data` for integrity errors - ❌ **Don't force batching**: If only 1-2 clearly separate issues exist, assign only those +- ❌ **Never assign pull requests**: `assign_to_agent` is for issues only — never pass a PR number ## skill: `issue-monster-report-formatting` --- diff --git a/actions/setup/js/assign_agent_helpers.cjs b/actions/setup/js/assign_agent_helpers.cjs index a0756435e97..b69f0c3d2cd 100644 --- a/actions/setup/js/assign_agent_helpers.cjs +++ b/actions/setup/js/assign_agent_helpers.cjs @@ -275,6 +275,11 @@ async function getIssueDetails(owner, repo, issueNumber, githubClient = github) core.error("Could not get issue data"); return null; } + // GitHub's issues API returns pull requests too; reject them here so callers + // never accidentally treat a PR as an assignable issue. + if (issue.pull_request) { + throw Object.assign(new Error(`#${issueNumber} is a pull request, not an issue — use pull_number instead of issue_number to assign to a pull request`), { isPullRequest: true }); + } const currentAssignees = (issue.assignees || []).map(assignee => ({ id: String(assignee.id), login: assignee.login, @@ -290,7 +295,9 @@ async function getIssueDetails(owner, repo, issueNumber, githubClient = github) }; } catch (error) { const errorMessage = getErrorMessage(error); - core.error(`Failed to get issue details: ${errorMessage}`); + if (!(/** @type {any} */ error.isPullRequest)) { + core.error(`Failed to get issue details: ${errorMessage}`); + } throw error; } } diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs index 1e415fc9c8e..9ea30500b7d 100644 --- a/actions/setup/js/assign_to_agent.cjs +++ b/actions/setup/js/assign_to_agent.cjs @@ -392,6 +392,24 @@ async function main(config = {}) { } catch (error) { let errorMessage = getErrorMessage(error); + // When the agent specified an issue_number that turns out to be a PR, skip + // silently without posting a comment — error comments on PRs are confusing. + if (/** @type {any} */ error.isPullRequest) { + core.warning(`Skipping assign_to_agent for #${number}: target is a pull request, not an issue.`); + _allResults.push({ + issue_number: issueNumber, + pull_number: pullNumber, + agent: agentName, + owner: effectiveOwner, + repo: effectiveRepo, + pull_request_repo: effectivePullRequestRepoSlug, + success: false, + skipped: true, + error: errorMessage, + }); + return { success: false, skipped: true, error: errorMessage }; + } + const isAuthError = ["Bad credentials", "Not Authenticated", "Resource not accessible", "Insufficient permissions", "requires authentication"].some(msg => errorMessage.includes(msg)); const isAvailabilityError = errorMessage.includes("coding agent is not available for this repository"); diff --git a/actions/setup/js/assign_to_agent.test.cjs b/actions/setup/js/assign_to_agent.test.cjs index 1c9b80e84f2..2426d4df9b6 100644 --- a/actions/setup/js/assign_to_agent.test.cjs +++ b/actions/setup/js/assign_to_agent.test.cjs @@ -1212,6 +1212,34 @@ describe("assign_to_agent", () => { expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled(); }); + it("should skip silently and not post a comment when issue_number resolves to a pull request", async () => { + setAgentOutput({ + items: [{ type: "assign_to_agent", issue_number: 99, agent: "copilot" }], + errors: [], + }); + + // Simulate the issues.get API returning a PR (has pull_request field) + mockGithub.rest.issues.get.mockResolvedValueOnce({ + data: { + id: 99999, + number: 99, + assignees: [], + html_url: "https://github.com/test-owner/test-repo/pull/99", + title: "Some PR", + body: "", + pull_request: { url: "https://api.github.com/repos/test-owner/test-repo/pulls/99" }, + }, + }); + + await eval(`(async () => { ${assignToAgentScript}; ${STANDALONE_RUNNER} })()`); + + // Should warn about the PR but not post a comment on it + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("pull request, not an issue")); + expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled(); + // Should not call setFailed — skipping a PR target is not a workflow failure + expect(mockCore.setFailed).not.toHaveBeenCalled(); + }); + it("should post failure comment on single failed assignment", async () => { setAgentOutput({ items: [{ type: "assign_to_agent", issue_number: 11, agent: "copilot" }],