Skip to content

Limit update_project item lookup to target content - #50839

Merged
pelikhan merged 7 commits into
mainfrom
copilot/update-project-pages
Aug 6, 2026
Merged

Limit update_project item lookup to target content#50839
pelikhan merged 7 commits into
mainfrom
copilot/update-project-pages

Conversation

Copilot AI commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

safe-outputs.update_project matched existing issue/PR items by scanning every ProjectV2.items page and expanding each item’s content. On mixed-repository org projects, that can require repo/PR read access unrelated to the target item.

  • Content-side lookup

    • Query Issue.projectItems / PullRequest.projectItems for the resolved content node.
    • Match only project items whose project.id matches the requested project.
  • Reduced permission blast radius

    • Avoid expanding content for unrelated board items.
    • Preserve addProjectV2ItemById when the target content is not already on the board.
  • Coverage

    • Added a focused test asserting issue updates use projectItems and do not query broad ProjectV2.items content.
node(id: $contentId) {
  ... on Issue {
    projectItems(first: 100, after: $after) {
      nodes {
        id
        project { id }
      }
    }
  }
}

Run: https://github.com/github/gh-aw/actions/runs/31113572782> Generated by 👨‍🍳 PR Sous Chef · gpt54 · 17.2 AIC · ⊞ 8.3K ·

Comment /souschef to run again

Copilot AI and others added 4 commits August 6, 2026 12:25
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix project item access issue in safe-outputs Limit update_project item lookup to target content Aug 6, 2026
Copilot AI requested a review from pelikhan August 6, 2026 12:31
@pelikhan
pelikhan marked this pull request as ready for review August 6, 2026 12:33
Copilot AI balanced review requested due to automatic review settings August 6, 2026 12:33
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Moves existing project-item lookup to the target issue or pull request, reducing unrelated repository permission requirements.

Changes:

  • Queries projectItems from target content and matches the requested project.
  • Preserves item creation when no match exists.
  • Updates fixtures and adds focused issue-side coverage.
Show a summary per file
File Description
actions/setup/js/update_project.cjs Implements content-side item lookup.
actions/setup/js/update_project.test.cjs Updates mocks and tests issue lookup behavior.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +396 to +397
... on PullRequest {
projectItems(first: 100, after: $after) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added finds an existing pull request item from the pull request side in cf04dbd. It asserts the item-lookup query contains the ... on PullRequest fragment (the mock throws otherwise), returns a matching item for the target project, and verifies addProjectV2ItemById is never called.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

The optimization looks correct. Querying projectItems from the content node (Issue/PullRequest) rather than scanning all project items is more efficient and avoids loading unrelated project items. The fragment placement and pagination logic are correct, and tests are updated consistently.> 🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 13.9 AIC · ⊞ 5.3K

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 60/100 — Acceptable

Analyzed 1 test(s): 1 design, 0 implementation, 0 violation(s).

📊 Metrics (1 test)
Metric Value
Analyzed 1 (Go: 0, JS: 1)
✅ Design 1 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 0 (0%)
Duplicate clusters 0
Inflation ⚠️ YES — test +88 / prod +27 ≈ 3.26:1 (threshold: 2:1)
🚨 Violations 0
Test File Classification Issues
finds an existing issue item from the issue side instead of scanning project content update_project.test.cjs:480 behavioral_contract / high_value / design_test No error-path coverage
⚠️ Flagged Tests (1)

finds an existing issue item from the issue side instead of scanning project content (update_project.test.cjs:480) — Strong design test: asserts the new content-side lookup uses projectItems( and does NOT scan content { across all project items, and that addProjectV2ItemById is not called when the item already exists. Enforces the key behavioral contract of this PR. Minor gap: no error-path scenario (e.g., projectItems returning entries for a different project ID) to verify the addProjectV2ItemById fallback is taken when needed.

📐 Inflation note

The test file gained 88 lines against 27 production lines (3.26:1). Most extra test lines come from updated response fixtures (emptyItemsResponse, existingItemResponse) that now include both items and projectItems shapes to match the new GraphQL response format. Legitimate fixture updates, but the 2:1 threshold is exceeded, costing 10 score points.

Verdict

⚠️ Passed with notes. 0% implementation tests (threshold: 30%). Score penalized for test inflation (3.26:1 ratio). No guideline violations. Core behavioral contract — content-side item lookup — is well covered by the new test.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 56.4 AIC · ⊞ 7.7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 60/100 — Acceptable. 0% implementation tests (threshold: 30%). Score penalized for test inflation (3.26:1). No violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Skills-Based Review 🧠

Applied /codebase-design and /tdd — two issues found, commenting rather than blocking.

📋 Key Themes & Highlights

Key Themes

  • Missing PullRequest content type test: the new focused test only exercises the Issue path; the PullRequest branch of the dual ... on Issue / ... on PullRequest query has no dedicated coverage.
  • Misleading warning on empty project items: result?.node?.projectItems being falsy can mean node-not-found or legitimately no project items — the current warning doesn't distinguish these cases.

Positive Highlights

  • ✅ Excellent security improvement: querying from the content side avoids expanding unrelated board items and reduces permission blast radius on org-wide projects.
  • ✅ The fragment ProjectItemProject is a clean, reusable abstraction that avoids duplication across the two type branches.
  • ✅ Comprehensive fixture updates keep all existing tests honest about the new query shape.
> 🧠 *Reviewed using Matt Pocock's skills by [Matt Pocock Skills Reviewer](https://github.com/github/gh-aw/actions/runs/31101934366)* · sonnet46 · 39.1 AIC · ⊞ 7.1K > Comment /matt to run again


it("adds a draft issue to a project board", async () => {
const projectUrl = "https://github.com/orgs/testowner/projects/60";
const output = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The new focused test only covers content_type: "issue", but the GraphQL query handles both ... on Issue and ... on PullRequest. A PR content type could silently fall back to addProjectV2ItemById without any test catching it.

💡 Suggested addition

Add a sibling test with content_type: "pull_request" that:

  • verifies the query contains projectItems(
  • verifies item-id is returned from the content-side lookup
  • asserts addProjectV2ItemById is not called

This closes the coverage gap between the two ... on Issue / ... on PullRequest branches in the new query.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a content_type: "pull_request" sibling test in cf04dbd that resolves the lookup only when the query contains ... on PullRequest and projectItems(, returns the existing item id, and asserts addProjectV2ItemById is not called.

const found = result.node.items.nodes.find(item => item.content?.id === contentId);
const found = projectItems.nodes.find(item => item.project?.id === projectId);
if (found) return found;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] result?.node?.projectItems silently breaks when the queried node is a PullRequest — GraphQL returns the data under result.node.projectItems for both types, so the path is the same, but the warning message says Content ${contentId} not found or inaccessible. If the content node exists but has no project items (legitimate empty result), this warning fires incorrectly.

💡 Suggested fix

Distinguish between node-not-found and empty-items:

if (!result?.node) {
  core.warning(`Content ${contentId} not found or inaccessible; stopping project item search.`);
  break;
}
const projectItems = result.node.projectItems;
if (!projectItems) break; // node exists but has no projectItems field — shouldn't happen

This prevents the misleading warning from firing on legitimate empty-project cases.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in cf04dbd: the warning now only fires when result.node is missing; a present node without projectItems just stops the search silently.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Verdict: Approve with non-blocking test-coverage gaps

The refactor from a broad ProjectV2.items scan to a targeted Issue.projectItems/PullRequest.projectItems lookup is correct and reduces the permission blast radius as intended. Core logic (pagination loop, cursor advancement, matching on project.id) is sound and the caller always resolves content to an Issue or PullRequest ID before invoking this function, so the "unsupported content type" concern raised by first-pass mining doesn't apply here.

💡 Themes and highlights
  • Correctness: query/fragment shape, pagination cursor handling, and warning-on-missing-node behavior are consistent with the prior implementation's guarantees.
  • Test gaps (non-blocking): the new dedicated regression test only covers the Issue fragment path with a single (non-paginated) response. Neither the PullRequest fragment branch with a found item, nor multi-page pagination through projectItems, are exercised by any test. Filed two review comments requesting coverage for both.
  • These are maintainability/test-debt concerns, not functional bugs in the shipped diff — they lower confidence in future regressions to this code path but don't block this PR.
> 🔎 *Code quality review by [PR Code Quality Reviewer](https://github.com/github/gh-aw/actions/runs/31101934367)* · auto · 99.6 AIC · ⊞ 7.8K > Comment /review to run again

orgProjectV2Response(projectUrl, 60, "project-pagination"),
issueResponse("issue-id-15"),
existingItemResponse("issue-id-15", "item-pagination"),
existingItemResponse("project-pagination", "item-pagination"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pagination test reuses the old single-page existingItemResponse for the item lookup, so the new paginated projectItems query path (multi-page hasNextPage: truefalse) is never actually exercised.

💡 Why this matters

The refactor changed the query shape (from ProjectV2.items to Issue/PullRequest.projectItems) and kept the same pagination loop structure, but no test drives projectItems.pageInfo.hasNextPage === true for more than one page. A regression such as forgetting to pass after: endCursor correctly, or an infinite loop if endCursor is not advanced, would go completely undetected — the only paginated test in this file (line 1151) exercises field pagination, not item-lookup pagination.

Suggested fix: add a dedicated test where findExistingItemByContentId receives two projectItems responses — first with hasNextPage: true and a cursor, second with the match — and assert both graphql calls happened with the correct after variable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added paginates content project items until the target project item is found in cf04dbd: the first projectItems page returns an item for a different project with hasNextPage: true, and the test asserts both lookup calls happened with after: null then after: "cursor-items-1".

}
}
}
... on PullRequest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new dedicated regression test only exercises the Issue inline fragment; the PullRequest branch of this query is completely untested, so a typo or schema drift in the PR-side fragment usage would silently break PR item lookups.

💡 Why this matters

Both ... on Issue and ... on PullRequest blocks duplicate the same projectItems(first: 100, after: $after) { nodes { ...ProjectItemProject } pageInfo {...} } shape. The existing pull_request test ("adds a pull request to the project board") uses emptyItemsResponse() which returns items/projectItems with empty nodes, so it never verifies that a found existing item is correctly returned via the PullRequest branch, nor that the query variables/shape are right for that branch.

Suggested fix: mirror the new issue-side test for content_type: "pull_request", asserting the query resolves via the PullRequest fragment and returns a matching item from existingItemResponse.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirrored the issue-side test for content_type: "pull_request" in cf04dbd; the mock only answers the lookup when the query contains the ... on PullRequest fragment, and the test asserts the found item is returned without addProjectV2ItemById.

@pelikhan

pelikhan commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Maintainer triage for this PR:

  • Please address the outstanding review follow-ups about coverage for the PullRequest content-type path and pagination through projectItems, and review whether the warning on missing projectItems should distinguish an unfetched node from a legitimate empty set.
  • Refresh the branch after the branch-update request completes.
  • Then run the pr-finisher skill and post a concise summary of what changed.

Run: https://github.com/github/gh-aw/actions/runs/31113572782

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 17.2 AIC · ⊞ 8.3K ·
Comment /souschef to run again

Copilot AI commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Maintainer triage for this PR:

  • Please address the outstanding review follow-ups about coverage for the PullRequest content-type path and pagination through projectItems, and review whe...

Ran pr-finisher. Summary:

Review follow-ups — already landed in cf04dbd, verified against the current head:

  • finds an existing pull request item from the pull request side — the mock only answers the lookup when the query contains ... on PullRequest and projectItems(, returns a matching item, and asserts addProjectV2ItemById is never called.
  • paginates content project items until the target project item is found — page 1 returns an item for a different project with hasNextPage: true; the test asserts both lookup calls used after: null then after: "cursor-items-1".
  • Warning condition — core.warning("Content … not found or inaccessible") now fires only when result.node is missing; a node present with a legitimately absent/empty projectItems just ends the search silently.

Branch refresh — the branch-update merge c876bb6 is in; PR base is current and there are no conflicts.

Local validation on the merged headmake fmt, make lint, make test-unit pass; update_project.test.cjs is green (123 tests). No Go files changed, so the impacted Go set was empty. No file changes were produced this run, so no new commit was pushed.

Status

  • ✅ Reviews — all five threads answered on their merits; I can't mark them resolved (no gh credentials in this sandbox), so that needs a maintainer click.
  • ✅ Checks (local) — green.
  • ❓ Checks (CI) — CJS is running on c876bb6; the cf04dbd runs are action_required (awaiting workflow approval), not failing. No prior failures to root-cause.
  • ❌ Mergeable — blocked pending the required review from @pelikhan.

No merge was performed.

Copilot AI requested a review from gh-aw-bot August 6, 2026 15:15
@pelikhan
pelikhan merged commit 4f6de95 into main Aug 6, 2026
9 of 10 checks passed
@pelikhan
pelikhan deleted the copilot/update-project-pages branch August 6, 2026 15:48
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.86.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update_project pages all ProjectV2 items and forces overly broad fine-grained PAT repo access

4 participants