Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
768932f
Initial plan
Copilot Feb 27, 2026
d33ab04
Initial plan for safe outputs schema fixes
Copilot Feb 27, 2026
e43e579
Fix safe outputs missing target-repo, allowed-repos, and github-token…
Copilot Feb 27, 2026
223cf96
Wire github-token and cross-repo fields through handler config and st…
Copilot Feb 27, 2026
8d7380e
Address code review: add comments for firstNonEmpty and token precede…
Copilot Feb 27, 2026
bbc11a3
cleanup project token
dsyme Feb 28, 2026
97a4707
Add handler_auth.cjs shared helper and consistent cross-repo/auth acr…
Copilot Feb 28, 2026
a595dbb
Merge branch 'main' into copilot/fix-safe-outputs-target-repo-support
dsyme Feb 28, 2026
c48945d
feat: extend createAuthenticatedGitHubClient to all remaining safe-ou…
Copilot Feb 28, 2026
dbfd7b0
Merge branch 'main' into copilot/fix-safe-outputs-target-repo-support
dsyme Feb 28, 2026
aae9308
Fix review comments: inaccurate comments, doc string, and handler_aut…
Copilot Feb 28, 2026
5a9cf76
Merge branch 'main' into copilot/fix-safe-outputs-target-repo-support
pelikhan Feb 28, 2026
2adf303
Merge branch 'main' into copilot/fix-safe-outputs-target-repo-support
pelikhan Feb 28, 2026
5128e5e
Add changeset [skip-ci]
Feb 28, 2026
3742d7a
ci: trigger CI checks
github-actions[bot] Feb 28, 2026
f5bc83b
Fix CI test failures: update aw_info_versions_test.go to use GH_AW_IN…
Copilot Feb 28, 2026
76411ab
test: Add smoke test file for Claude engine validation (run 22526414043)
claude Feb 28, 2026
724115a
ci: trigger CI checks
github-actions[bot] Feb 28, 2026
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
5 changes: 5 additions & 0 deletions .changeset/patch-fix-safe-output-cross-repo-auth.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .github/smoke-test-claude-22526414043.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Smoke Test File

Created by smoke test run 22526414043 for Claude engine validation.
19 changes: 12 additions & 7 deletions actions/setup/js/add_comment.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");
const { resolveTarget } = require("./safe_output_helpers.cjs");
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
Copy link
Contributor

Choose a reason for hiding this comment

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

Good pattern — importing createAuthenticatedGitHubClient here ensures all GitHub API calls in this file use the correct token for cross-repo operations.

const { getMissingInfoSections } = require("./missing_messages_helper.cjs");
const { getMessages } = require("./messages_core.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
Expand Down Expand Up @@ -302,6 +303,10 @@ async function main(config = {}) {
const maxCount = config.max || 20;
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);

// Create an authenticated GitHub client. Uses config["github-token"] when set
Copy link
Contributor

Choose a reason for hiding this comment

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

Creating authClient once at the top of main() and reusing it throughout is clean and avoids redundant auth setup on each API call.

// (for cross-repository operations), otherwise falls back to the step-level github.
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";

Expand Down Expand Up @@ -453,7 +458,7 @@ async function main(config = {}) {
if (item.item_number !== undefined && item.item_number !== null) {
// Explicit item_number: fetch the issue/PR to get its author
try {
const { data: issueData } = await github.rest.issues.get({
const { data: issueData } = await authClient.rest.issues.get({
owner: repoParts.owner,
repo: repoParts.repo,
issue_number: itemNumber,
Expand Down Expand Up @@ -556,7 +561,7 @@ async function main(config = {}) {
// Hide older comments if enabled AND append-only-comments is not enabled
// When append-only-comments is true, we want to keep all comments visible
if (hideOlderCommentsEnabled && !appendOnlyComments && workflowId) {
await hideOlderComments(github, repoParts.owner, repoParts.repo, itemNumber, workflowId, isDiscussion);
await hideOlderComments(authClient, repoParts.owner, repoParts.repo, itemNumber, workflowId, isDiscussion);
} else if (hideOlderCommentsEnabled && appendOnlyComments) {
core.info("Skipping hide-older-comments because append-only-comments is enabled");
}
Expand All @@ -574,7 +579,7 @@ async function main(config = {}) {
}
}
`;
const queryResult = await github.graphql(discussionQuery, {
const queryResult = await authClient.graphql(discussionQuery, {
owner: repoParts.owner,
repo: repoParts.repo,
number: itemNumber,
Expand All @@ -585,10 +590,10 @@ async function main(config = {}) {
throw new Error(`${ERR_NOT_FOUND}: Discussion #${itemNumber} not found in ${itemRepo}`);
}

comment = await commentOnDiscussion(github, repoParts.owner, repoParts.repo, itemNumber, processedBody, null);
comment = await commentOnDiscussion(authClient, repoParts.owner, repoParts.repo, itemNumber, processedBody, null);
} else {
// Use REST API for issues/PRs
const { data } = await github.rest.issues.createComment({
const { data } = await authClient.rest.issues.createComment({
owner: repoParts.owner,
repo: repoParts.repo,
issue_number: itemNumber,
Expand Down Expand Up @@ -644,7 +649,7 @@ async function main(config = {}) {
}
}
`;
const queryResult = await github.graphql(discussionQuery, {
const queryResult = await authClient.graphql(discussionQuery, {
owner: repoParts.owner,
repo: repoParts.repo,
number: itemNumber,
Expand All @@ -656,7 +661,7 @@ async function main(config = {}) {
}

core.info(`Found discussion #${itemNumber}, adding comment...`);
const comment = await commentOnDiscussion(github, repoParts.owner, repoParts.repo, itemNumber, processedBody, null);
const comment = await commentOnDiscussion(authClient, repoParts.owner, repoParts.repo, itemNumber, processedBody, null);

core.info(`Created comment on discussion: ${comment.html_url}`);

Expand Down
4 changes: 3 additions & 1 deletion actions/setup/js/add_labels.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");

/**
* Maximum limits for label parameters to prevent resource exhaustion.
Expand All @@ -32,6 +33,7 @@ async function main(config = {}) {
const blockedPatterns = config.blocked || [];
const maxCount = config.max || 10;
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down Expand Up @@ -161,7 +163,7 @@ async function main(config = {}) {
}

try {
await github.rest.issues.addLabels({
await authClient.rest.issues.addLabels({
owner: repoParts.owner,
repo: repoParts.repo,
issue_number: itemNumber,
Expand Down
6 changes: 4 additions & 2 deletions actions/setup/js/add_reviewer.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { processItems } = require("./safe_output_processor.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { getPullRequestNumber } = require("./pr_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");

// GitHub Copilot reviewer bot username
const COPILOT_REVIEWER_BOT = "copilot-pull-request-reviewer[bot]";
Expand All @@ -25,6 +26,7 @@ async function main(config = {}) {
// Extract configuration
const allowedReviewers = config.allowed || [];
const maxCount = config.max || 10;
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down Expand Up @@ -106,7 +108,7 @@ async function main(config = {}) {

// Add non-copilot reviewers first
if (otherReviewers.length > 0) {
await github.rest.pulls.requestReviewers({
await authClient.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
Expand All @@ -118,7 +120,7 @@ async function main(config = {}) {
// Add copilot reviewer separately if requested
if (hasCopilot) {
try {
await github.rest.pulls.requestReviewers({
await authClient.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
Expand Down
6 changes: 4 additions & 2 deletions actions/setup/js/assign_milestone.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

const { getErrorMessage } = require("./error_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");

/** @type {string} Safe output type handled by this module */
const HANDLER_TYPE = "assign_milestone";
Expand All @@ -20,6 +21,7 @@ async function main(config = {}) {
// Extract configuration
const allowedMilestones = config.allowed || [];
const maxCount = config.max || 10;
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down Expand Up @@ -77,7 +79,7 @@ async function main(config = {}) {
// Fetch milestones if needed and not already cached
if (allowedMilestones.length > 0 && allMilestones === null) {
try {
const milestonesResponse = await github.rest.issues.listMilestones({
const milestonesResponse = await authClient.rest.issues.listMilestones({
owner: context.repo.owner,
repo: context.repo.repo,
state: "all",
Expand Down Expand Up @@ -133,7 +135,7 @@ async function main(config = {}) {
};
}

await github.rest.issues.update({
await authClient.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
Expand Down
8 changes: 5 additions & 3 deletions actions/setup/js/assign_to_user.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_help
const { resolveIssueNumber, extractAssignees } = require("./safe_output_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");

/** @type {string} Safe output type handled by this module */
const HANDLER_TYPE = "assign_to_user";
Expand All @@ -27,6 +28,7 @@ async function main(config = {}) {
const maxCount = config.max || 10;
const unassignFirst = parseBoolTemplatable(config.unassign_first, false);
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down Expand Up @@ -131,7 +133,7 @@ async function main(config = {}) {
// If unassign_first is enabled, get current assignees and remove them first
if (unassignFirst) {
core.info(`Fetching current assignees for issue #${issueNumber} to unassign them first`);
const issue = await github.rest.issues.get({
const issue = await authClient.rest.issues.get({
owner: repoParts.owner,
repo: repoParts.repo,
issue_number: issueNumber,
Expand All @@ -140,7 +142,7 @@ async function main(config = {}) {
const currentAssignees = issue.data.assignees?.map(a => a.login) || [];
if (currentAssignees.length > 0) {
core.info(`Unassigning ${currentAssignees.length} current assignee(s): ${JSON.stringify(currentAssignees)}`);
await github.rest.issues.removeAssignees({
await authClient.rest.issues.removeAssignees({
owner: repoParts.owner,
repo: repoParts.repo,
issue_number: issueNumber,
Expand All @@ -153,7 +155,7 @@ async function main(config = {}) {
}

// Add assignees to the issue
await github.rest.issues.addAssignees({
await authClient.rest.issues.addAssignees({
owner: repoParts.owner,
repo: repoParts.repo,
issue_number: issueNumber,
Expand Down
8 changes: 5 additions & 3 deletions actions/setup/js/close_discussion.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
const { ERR_NOT_FOUND } = require("./error_codes.cjs");

/**
Expand Down Expand Up @@ -159,6 +160,7 @@ async function main(config = {}) {
const requiredLabels = config.required_labels || [];
const requiredTitlePrefix = config.required_title_prefix || "";
const maxCount = config.max || 10;
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down Expand Up @@ -218,7 +220,7 @@ async function main(config = {}) {

try {
// Fetch discussion details
const discussion = await getDiscussionDetails(github, context.repo.owner, context.repo.repo, discussionNumber);
const discussion = await getDiscussionDetails(authClient, context.repo.owner, context.repo.repo, discussionNumber);

// Validate required labels if configured
if (requiredLabels.length > 0) {
Expand Down Expand Up @@ -266,7 +268,7 @@ async function main(config = {}) {
let commentUrl;
if (item.body) {
const sanitizedBody = sanitizeContent(item.body);
const comment = await addDiscussionComment(github, discussion.id, sanitizedBody);
const comment = await addDiscussionComment(authClient, discussion.id, sanitizedBody);
core.info(`Added comment to discussion #${discussionNumber}: ${comment.url}`);
commentUrl = comment.url;
}
Expand All @@ -277,7 +279,7 @@ async function main(config = {}) {
} else {
const reason = item.reason || undefined;
core.info(`Closing discussion #${discussionNumber} with reason: ${reason || "none"}`);
const closedDiscussion = await closeDiscussion(github, discussion.id, reason);
const closedDiscussion = await closeDiscussion(authClient, discussion.id, reason);
core.info(`Closed discussion #${discussionNumber}: ${closedDiscussion.url}`);
}

Expand Down
8 changes: 5 additions & 3 deletions actions/setup/js/close_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
const { ERR_NOT_FOUND } = require("./error_codes.cjs");

/**
Expand Down Expand Up @@ -87,6 +88,7 @@ async function main(config = {}) {
const comment = config.comment || "";
const configStateReason = config.state_reason || "COMPLETED";
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down Expand Up @@ -200,7 +202,7 @@ async function main(config = {}) {
try {
// Fetch issue details
core.info(`Fetching issue details for #${issueNumber} in ${repoParts.owner}/${repoParts.repo}`);
const issue = await getIssueDetails(github, repoParts.owner, repoParts.repo, issueNumber);
const issue = await getIssueDetails(authClient, repoParts.owner, repoParts.repo, issueNumber);
core.info(`Issue #${issueNumber} fetched: state=${issue.state}, title="${issue.title}", labels=[${issue.labels.map(l => l.name || l).join(", ")}]`);

// Check if already closed - but still add comment
Expand Down Expand Up @@ -252,7 +254,7 @@ async function main(config = {}) {

// Add comment with the body from the message
core.info(`Adding comment to issue #${issueNumber}: length=${commentToPost.length}`);
const commentResult = await addIssueComment(github, repoParts.owner, repoParts.repo, issueNumber, commentToPost);
const commentResult = await addIssueComment(authClient, repoParts.owner, repoParts.repo, issueNumber, commentToPost);
core.info(`✓ Comment posted to issue #${issueNumber}: ${commentResult.html_url}`);
core.info(`Comment details: id=${commentResult.id}, body_length=${commentToPost.length}`);

Expand All @@ -265,7 +267,7 @@ async function main(config = {}) {
// Use item-level state_reason if provided, otherwise fall back to config-level default
const stateReason = item.state_reason || configStateReason;
core.info(`Closing issue #${issueNumber} in ${itemRepo} with state_reason=${stateReason}`);
closedIssue = await closeIssue(github, repoParts.owner, repoParts.repo, issueNumber, stateReason);
closedIssue = await closeIssue(authClient, repoParts.owner, repoParts.repo, issueNumber, stateReason);
core.info(`✓ Issue #${issueNumber} closed successfully: ${closedIssue.html_url}`);
}

Expand Down
8 changes: 5 additions & 3 deletions actions/setup/js/close_pull_request.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { getTrackerID } = require("./get_tracker_id.cjs");
const { generateFooterWithMessages } = require("./messages_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
const { ERR_NOT_FOUND } = require("./error_codes.cjs");

/**
Expand Down Expand Up @@ -85,6 +86,7 @@ async function main(config = {}) {
const requiredTitlePrefix = config.required_title_prefix || "";
const maxCount = config.max || 10;
const comment = config.comment || "";
const authClient = await createAuthenticatedGitHubClient(config);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down Expand Up @@ -185,7 +187,7 @@ async function main(config = {}) {
let pr;
try {
core.info(`Fetching PR details for #${prNumber} in ${owner}/${repo}`);
pr = await getPullRequestDetails(github, owner, repo, prNumber);
pr = await getPullRequestDetails(authClient, owner, repo, prNumber);
core.info(`PR #${prNumber} fetched: state=${pr.state}, title="${pr.title}", labels=[${pr.labels.map(l => l.name || l).join(", ")}]`);
} catch (error) {
const errorMsg = getErrorMessage(error);
Expand Down Expand Up @@ -247,7 +249,7 @@ async function main(config = {}) {
const triggeringIssueNumber = context.payload?.issue?.number;
const commentBody = buildCommentBody(commentToPost, triggeringIssueNumber, triggeringPRNumber);
core.info(`Adding comment to PR #${prNumber}: length=${commentBody.length}`);
await addPullRequestComment(github, owner, repo, prNumber, commentBody);
await addPullRequestComment(authClient, owner, repo, prNumber, commentBody);
commentPosted = true;
core.info(`✓ Comment posted to PR #${prNumber}`);
core.info(`Comment details: body_length=${commentBody.length}`);
Expand All @@ -273,7 +275,7 @@ async function main(config = {}) {
} else {
try {
core.info(`Closing PR #${prNumber}`);
closedPR = await closePullRequest(github, owner, repo, prNumber);
closedPR = await closePullRequest(authClient, owner, repo, prNumber);
core.info(`✓ PR #${prNumber} closed successfully: ${closedPR.title}`);
} catch (error) {
const errorMsg = getErrorMessage(error);
Expand Down
Loading
Loading