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
25 changes: 2 additions & 23 deletions actions/setup/js/set_issue_field.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function getIssueNodeId(githubClient, owner, repo, issueNumber) {
}

/**
* Fetches available issue fields for the repository/owner.
* Fetches available issue fields for the repository.

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.

[/grill-with-docs] The JSDoc update from repository/owner to repository correctly reflects the scope reduction, but it doesn't explain why org-level fields are excluded.

💡 Suggested addition
/**
 * Fetches available issue fields for the repository.
 * `@note` Intentionally scoped to repository-level fields only. The org-level
 *   `issueFields` query (`repository.owner { ... on Organization }`) fails
 *   with `GITHUB_TOKEN` ("Resource not accessible by integration") since
 *   Actions tokens only carry `issues: write` at the repo level.
 * `@param` {Object} githubClient ...

Without this context, a future contributor might re-add org discovery as a "fix" and reintroduce the permission error.

@copilot please address this.

* @param {Object} githubClient - Authenticated GitHub client
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
Expand All @@ -55,35 +55,14 @@ async function fetchIssueFields(githubClient, owner, repo) {
... on IssueFieldMultiSelect { id name options { id name } }
}
}
owner {
__typename
... on Organization {
issueFields(first: 100) {
nodes {
__typename
... on IssueFieldText { id name }
... on IssueFieldNumber { id name }
... on IssueFieldDate { id name }
... on IssueFieldSingleSelect { id name options { id name } }
... on IssueFieldMultiSelect { id name options { id name } }
}
}
}
}
}
}`,
{ owner, repo }
);

const isValidNode = node => typeof node?.id === "string" && typeof node?.name === "string";

const repoFields = (result?.repository?.issueFields?.nodes ?? []).filter(isValidNode);
if (repoFields.length > 0) {
return repoFields;
}

const ownerFields = (result?.repository?.owner?.issueFields?.nodes ?? []).filter(isValidNode);
return ownerFields;
return (result?.repository?.issueFields?.nodes ?? []).filter(isValidNode);

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 simplification is clean and correct for GITHUB_TOKEN, but this removes the org-level field fallback entirely — a silent behavioral regression for callers using a PAT or GitHub App token with org-read access.

Previously, if repository.issueFields returned empty, discovery fell back to Organization.issueFields. That path is now gone: a user whose custom fields live at the org level will get an empty result and a confusing "field not found" error with no indication of why.

Consider:

  • Documenting this as a breaking change in a CHANGELOG or the action README, or
  • Offering an org_fallback input (default false for GITHUB_TOKEN safety) so PAT/GitHub App callers can opt back in.

@copilot please address this.

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.

[/diagnosing-bugs] When fetchIssueFields returns an empty array, the caller will eventually throw a confusing "field not found" error with no indication that field discovery came back empty.

💡 Suggested improvement

Consider adding a warning log before the return:

const fields = (result?.repository?.issueFields?.nodes ?? []).filter(isValidNode);
if (fields.length === 0) {
  console.warn(`[set_issue_field] fetchIssueFields: no valid fields found for ${owner}/${repo}`);
}
return fields;

This surfaces the failure point earlier, especially during first-time setup when a misconfigured project or insufficient token scope would otherwise produce a silent empty result followed by a cryptic downstream error.

@copilot please address this.

}

/**
Expand Down
7 changes: 2 additions & 5 deletions actions/setup/js/set_issue_field.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,6 @@ describe("set_issue_field (Handler Factory Architecture)", () => {
return Promise.resolve({
repository: {
issueFields: { nodes: [] },
owner: {
__typename: "Organization",
issueFields: { nodes: [] },
},
},
});
}
Expand Down Expand Up @@ -349,6 +345,8 @@ describe("set_issue_field (Handler Factory Architecture)", () => {
await h({ type: "set_issue_field", issue_number: 42, field_name: "Customer Impact", value: "High" }, {});

expect(capturedQuery).not.toContain("... on User");
expect(capturedQuery).not.toContain("... on Organization");

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] Good negative assertions — these prevent the problematic query patterns from quietly re-appearing. Consider also adding a case that verifies fetchIssueFields returns [] when result.repository.issueFields is null (not just { nodes: [] }), which can happen when the field doesn't exist in the GraphQL schema for a specific GitHub plan.

💡 Sketch
it('fetchIssueFields returns [] when issueFields is null', async () => {
  const mockClient = {
    graphql: () => Promise.resolve({ repository: { issueFields: null } })
  };
  const result = await fetchIssueFields(mockClient, 'owner', 'repo');
  expect(result).toEqual([]);
});

The ?? [] guard already handles this safely, but making it explicit in the test suite documents the expected behaviour and catches future regressions if the guard is accidentally removed.

@copilot please address this.

expect(capturedQuery).not.toMatch(/owner\s*\{/);
});

it("fetchIssueFields filters out nodes missing id or name (null entries and unknown types)", async () => {
Expand All @@ -365,7 +363,6 @@ describe("set_issue_field (Handler Factory Architecture)", () => {
{ __typename: "IssueFieldUnknown", name: "Orphan" }, // missing id
],
},
owner: { __typename: "Organization", issueFields: { nodes: [] } },
},
});
}
Expand Down
15 changes: 0 additions & 15 deletions actions/setup/js/set_issue_field_api_query.integration.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,6 @@ const ISSUE_FIELDS_DISCOVERY_QUERY = `query($owner: String!, $repo: String!) {
... on IssueFieldMultiSelect { id name options { id name } }
}
}
owner {
__typename
... on Organization {
issueFields(first: 100) {
nodes {
__typename
... on IssueFieldText { id name }
... on IssueFieldNumber { id name }
... on IssueFieldDate { id name }
... on IssueFieldSingleSelect { id name options { id name } }
... on IssueFieldMultiSelect { id name options { id name } }
}
}
}
}
}
}`;

Expand Down
Loading