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
9 changes: 8 additions & 1 deletion actions/setup/js/safe_outputs_tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
},
{
"name": "update_discussion",
"description": "Update an existing GitHub discussion's title or body. Use this to modify discussion properties after creation. Only the fields you specify will be updated; other fields remain unchanged.",
"description": "Update an existing GitHub discussion's title, body, or labels. Use this to modify discussion properties after creation. Only the fields you specify will be updated; other fields remain unchanged.",
"inputSchema": {
"type": "object",
"properties": {
Expand All @@ -110,6 +110,13 @@
"type": "string",
"description": "New discussion body to replace the existing content. Use Markdown formatting."
},
"labels": {
"type": "array",
"items": {
"type": "string"
},
"description": "Replace the discussion labels with this list (e.g., [\"bug\", \"tracking:foo\"]). Labels must exist in the repository."
},
"discussion_number": {
"type": ["number", "string"],
"description": "Discussion number to update. This is the numeric ID from the GitHub URL (e.g., 345 in github.com/owner/repo/discussions/345). Required when the workflow target is '*' (any discussion)."
Expand Down
57 changes: 37 additions & 20 deletions actions/setup/js/update_discussion.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -143,37 +143,48 @@ async function executeDiscussionUpdate(github, context, discussionNumber, update
throw new Error(`${ERR_NOT_FOUND}: Discussion #${discussionNumber} not found`);
}

// Build mutation for updating discussion
let mutation = `
mutation($discussionId: ID!, $title: String, $body: String) {
updateDiscussion(input: { discussionId: $discussionId, title: $title, body: $body }) {
discussion {
id
title
body
url
const hasTitleUpdate = updateData.title !== undefined;
const hasBodyUpdate = updateData.body !== undefined;
const hasLabelsUpdate = updateData.labels !== undefined;

let updatedDiscussion = discussion;

// Only call the updateDiscussion mutation when title or body actually needs updating.
// Skipping this when only labels are being changed avoids accidentally modifying
// the discussion body with stale or unexpected content.
if (hasTitleUpdate || hasBodyUpdate) {
const mutation = `
mutation($discussionId: ID!, $title: String, $body: String) {
updateDiscussion(input: { discussionId: $discussionId, title: $title, body: $body }) {
discussion {
id
title
body
url
}
}
}
}
`;
`;

const variables = {
discussionId: discussion.id,
title: updateData.title || discussion.title,
body: updateData.body || discussion.body,
};
const variables = {
discussionId: discussion.id,
title: hasTitleUpdate ? updateData.title : discussion.title,
body: hasBodyUpdate ? updateData.body : discussion.body,
};

const mutationResult = await github.graphql(mutation, variables);
const updatedDiscussion = mutationResult.updateDiscussion.discussion;
const mutationResult = await github.graphql(mutation, variables);
updatedDiscussion = mutationResult.updateDiscussion.discussion;
}

// Handle label replacement if labels were provided
if (updateData.labels !== undefined) {
if (hasLabelsUpdate) {
try {
const labelIds = await fetchLabelNodeIds(github, context.repo.owner, context.repo.repo, updateData.labels);
await replaceDiscussionLabels(github, discussion.id, labelIds);
core.info(`Successfully replaced labels on discussion #${discussionNumber}`);
} catch (error) {
core.warning(`Discussion #${discussionNumber} title/body updated successfully, but label update failed: ${getErrorMessage(error)}`);
const context = hasTitleUpdate || hasBodyUpdate ? "title/body updated successfully but " : "";
core.warning(`Discussion #${discussionNumber} ${context}label update failed: ${getErrorMessage(error)}`);
Comment on lines +186 to +187
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

In the label-update error handler, const context = ... shadows the context parameter of executeDiscussionUpdate(...). This makes the code harder to read and can easily lead to mistakes if the surrounding block later needs the Actions context. Rename the local string variable (e.g. updateContextMsg / prefix) to avoid shadowing.

Suggested change
const context = hasTitleUpdate || hasBodyUpdate ? "title/body updated successfully but " : "";
core.warning(`Discussion #${discussionNumber} ${context}label update failed: ${getErrorMessage(error)}`);
const updateContextMsg = hasTitleUpdate || hasBodyUpdate ? "title/body updated successfully but " : "";
core.warning(`Discussion #${discussionNumber} ${updateContextMsg}label update failed: ${getErrorMessage(error)}`);

Copilot uses AI. Check for mistakes.
}
}

Expand Down Expand Up @@ -240,10 +251,16 @@ function buildDiscussionUpdateData(item, config) {
const updateData = {};

if (item.title !== undefined) {
if (config.allow_title !== true) {
return { success: false, error: "Title updates are not allowed by the safe-outputs configuration" };
}
// Sanitize title for Unicode security (no prefix handling needed for updates)
updateData.title = sanitizeTitle(item.title);
}
if (item.body !== undefined) {
if (config.allow_body !== true) {
return { success: false, error: "Body updates are not allowed by the safe-outputs configuration" };
}
updateData.body = item.body;
}

Expand Down
Loading
Loading