Skip to content

Commit d478fe1

Browse files
chore: sync actions from gh-aw@v0.65.1 (#57)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 4b6ffdf commit d478fe1

7 files changed

Lines changed: 156 additions & 100 deletions

setup-cli/install.sh

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
# Script to download and install gh-aw binary for the current OS and architecture
44
# Supports: Linux, macOS (Darwin), FreeBSD, Windows (Git Bash/MSYS/Cygwin)
55
# Usage: ./install-gh-aw.sh [version] [options]
6-
# If no version is specified, it will use "latest" (GitHub automatically resolves to the latest release)
6+
# If no version is specified, it will use "stable" (resolved from .github/aw/releases.json)
7+
# Version aliases (e.g. "stable", "latest") are resolved via .github/aw/releases.json before download.
78
# Note: Checksum validation is currently skipped by default (will be enabled in future releases)
89
#
910
# Examples:
10-
# ./install-gh-aw.sh # Install latest version
11+
# ./install-gh-aw.sh # Install stable version
12+
# ./install-gh-aw.sh latest # Install latest version
1113
# ./install-gh-aw.sh v1.0.0 # Install specific version
1214
# ./install-gh-aw.sh --skip-checksum # Skip checksum validation
1315
#
@@ -224,17 +226,49 @@ fetch_release_data() {
224226
return 1
225227
}
226228

227-
# Get version (use provided version or default to "latest")
229+
# Get version (use provided version or default to "stable")
228230
# VERSION is already set from argument parsing
229231
REPO="github/gh-aw"
230232

231233
if [ -z "$VERSION" ]; then
232-
print_info "No version specified, using 'latest'..."
233-
VERSION="latest"
234+
print_info "No version specified, using 'stable'..."
235+
VERSION="stable"
234236
else
235237
print_info "Using specified version: $VERSION"
236238
fi
237239

240+
# Resolve version aliases from releases.json
241+
RELEASES_JSON_URL="https://raw.githubusercontent.com/$REPO/main/.github/aw/releases.json"
242+
print_info "Resolving version alias '$VERSION' from $RELEASES_JSON_URL..."
243+
244+
releases_json=""
245+
releases_json=$(curl -s -f "$RELEASES_JSON_URL" 2>/dev/null) || true
246+
247+
if [ -n "$releases_json" ]; then
248+
resolved_version=""
249+
if [ "$HAS_JQ" = true ]; then
250+
resolved_version=$(echo "$releases_json" | jq -r ".aliases[\"$VERSION\"] // empty" 2>/dev/null) || {
251+
print_info "jq failed to parse releases.json; alias resolution skipped"
252+
}
253+
else
254+
# Fallback: extract alias value using grep/sed
255+
# Escape regex special characters in VERSION to avoid unintended matches
256+
version_escaped=$(printf '%s' "$VERSION" | sed 's/[.[\*^$]/\\&/g')
257+
resolved_version=$(echo "$releases_json" | grep -o "\"${version_escaped}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | sed 's/.*:[[:space:]]*"\([^"]*\)"/\1/' | head -1) || true
258+
fi
259+
260+
if [ -n "$resolved_version" ] && [ "$resolved_version" != "$VERSION" ]; then
261+
print_info "Resolved alias '$VERSION' -> '$resolved_version'"
262+
VERSION="$resolved_version"
263+
elif [ -n "$resolved_version" ]; then
264+
print_warning "Version '$VERSION' is an alias for itself in releases.json (no change); this may indicate a misconfiguration"
265+
else
266+
print_info "No alias found for '$VERSION', using it as-is"
267+
fi
268+
else
269+
print_warning "Could not fetch releases.json; proceeding with version '$VERSION' as-is"
270+
fi
271+
238272
# Try gh extension install if requested (and gh is available)
239273
if [ "$TRY_GH_INSTALL" = true ] && command -v gh &> /dev/null; then
240274
print_info "Attempting to install gh-aw using 'gh extension install'..."

setup/js/add_reaction.cjs

Lines changed: 85 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -36,98 +36,114 @@ async function main() {
3636
}
3737

3838
// Determine the API endpoint based on the event type
39-
let reactionEndpoint;
4039
const eventName = context.eventName;
4140
const owner = context.repo.owner;
4241
const repo = context.repo.repo;
4342

44-
try {
45-
switch (eventName) {
46-
case "issues": {
47-
const issueNumber = context.payload?.issue?.number;
48-
if (!issueNumber) {
49-
core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
50-
return;
51-
}
52-
reactionEndpoint = `/repos/${owner}/${repo}/issues/${issueNumber}/reactions`;
53-
break;
43+
/** @type {string | undefined} */
44+
let reactionEndpoint;
45+
46+
switch (eventName) {
47+
case "issues": {
48+
const issueNumber = context.payload?.issue?.number;
49+
if (!issueNumber) {
50+
core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
51+
return;
5452
}
53+
reactionEndpoint = `/repos/${owner}/${repo}/issues/${issueNumber}/reactions`;
54+
break;
55+
}
5556

56-
case "issue_comment": {
57-
const commentId = context.payload?.comment?.id;
58-
if (!commentId) {
59-
core.setFailed(`${ERR_VALIDATION}: Comment ID not found in event payload`);
60-
return;
61-
}
62-
reactionEndpoint = `/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`;
63-
break;
57+
case "issue_comment": {
58+
const commentId = context.payload?.comment?.id;
59+
if (!commentId) {
60+
core.setFailed(`${ERR_VALIDATION}: Comment ID not found in event payload`);
61+
return;
6462
}
63+
reactionEndpoint = `/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`;
64+
break;
65+
}
6566

66-
case "pull_request": {
67-
const prNumber = context.payload?.pull_request?.number;
68-
if (!prNumber) {
69-
core.setFailed(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
70-
return;
71-
}
72-
// PRs are "issues" for the reactions endpoint
73-
reactionEndpoint = `/repos/${owner}/${repo}/issues/${prNumber}/reactions`;
74-
break;
67+
case "pull_request": {
68+
const prNumber = context.payload?.pull_request?.number;
69+
if (!prNumber) {
70+
core.setFailed(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
71+
return;
7572
}
73+
// PRs are "issues" for the reactions endpoint
74+
reactionEndpoint = `/repos/${owner}/${repo}/issues/${prNumber}/reactions`;
75+
break;
76+
}
7677

77-
case "pull_request_review_comment": {
78-
const reviewCommentId = context.payload?.comment?.id;
79-
if (!reviewCommentId) {
80-
core.setFailed(`${ERR_VALIDATION}: Review comment ID not found in event payload`);
81-
return;
82-
}
83-
reactionEndpoint = `/repos/${owner}/${repo}/pulls/comments/${reviewCommentId}/reactions`;
84-
break;
78+
case "pull_request_review_comment": {
79+
const reviewCommentId = context.payload?.comment?.id;
80+
if (!reviewCommentId) {
81+
core.setFailed(`${ERR_VALIDATION}: Review comment ID not found in event payload`);
82+
return;
8583
}
84+
reactionEndpoint = `/repos/${owner}/${repo}/pulls/comments/${reviewCommentId}/reactions`;
85+
break;
86+
}
8687

87-
case "discussion": {
88-
const discussionNumber = context.payload?.discussion?.number;
89-
if (!discussionNumber) {
90-
core.setFailed(`${ERR_NOT_FOUND}: Discussion number not found in event payload`);
91-
return;
92-
}
93-
// Discussions use GraphQL API - get the node ID
88+
case "discussion": {
89+
const discussionNumber = context.payload?.discussion?.number;
90+
if (!discussionNumber) {
91+
core.setFailed(`${ERR_NOT_FOUND}: Discussion number not found in event payload`);
92+
return;
93+
}
94+
// Discussions use GraphQL API - get the node ID
95+
try {
9496
const discussionNodeId = await getDiscussionNodeId(owner, repo, discussionNumber);
9597
await addDiscussionReaction(discussionNodeId, reaction);
96-
return; // Early return for discussion events
98+
} catch (error) {
99+
handleReactionError(error);
97100
}
101+
return;
102+
}
98103

99-
case "discussion_comment": {
100-
const commentNodeId = context.payload?.comment?.node_id;
101-
if (!commentNodeId) {
102-
core.setFailed(`${ERR_NOT_FOUND}: Discussion comment node ID not found in event payload`);
103-
return;
104-
}
104+
case "discussion_comment": {
105+
const commentNodeId = context.payload?.comment?.node_id;
106+
if (!commentNodeId) {
107+
core.setFailed(`${ERR_NOT_FOUND}: Discussion comment node ID not found in event payload`);
108+
return;
109+
}
110+
try {
105111
await addDiscussionReaction(commentNodeId, reaction);
106-
return; // Early return for discussion comment events
112+
} catch (error) {
113+
handleReactionError(error);
107114
}
108-
109-
default:
110-
core.setFailed(`${ERR_VALIDATION}: Unsupported event type: ${eventName}`);
111-
return;
115+
return;
112116
}
113117

114-
// Add reaction using REST API (for non-discussion events)
115-
core.info(`Adding reaction to: ${reactionEndpoint}`);
116-
await addReaction(/** @type {string} */ reactionEndpoint, reaction);
117-
} catch (error) {
118-
const errorMessage = getErrorMessage(error);
119-
120-
// Check if the error is due to a locked issue/PR/discussion
121-
if (isLockedError(error)) {
122-
// Silently ignore locked resource errors - just log for debugging
123-
core.info(`Cannot add reaction: resource is locked (this is expected and not an error)`);
118+
default:
119+
core.setFailed(`${ERR_VALIDATION}: Unsupported event type: ${eventName}`);
124120
return;
125-
}
121+
}
126122

127-
// For other errors, fail as before
128-
core.error(`Failed to add reaction: ${errorMessage}`);
129-
core.setFailed(`${ERR_API}: Failed to add reaction: ${errorMessage}`);
123+
// Add reaction using REST API (for non-discussion events)
124+
// reactionEndpoint is always defined here - all other cases return early
125+
if (!reactionEndpoint) return;
126+
core.info(`Adding reaction to: ${reactionEndpoint}`);
127+
try {
128+
await addReaction(reactionEndpoint, reaction);
129+
} catch (error) {
130+
handleReactionError(error);
131+
}
132+
}
133+
134+
/**
135+
* Handle errors from reaction API calls consistently
136+
* @param {unknown} error - The error to handle
137+
*/
138+
function handleReactionError(error) {
139+
if (isLockedError(error)) {
140+
// Silently ignore locked resource errors - just log for debugging
141+
core.info(`Cannot add reaction: resource is locked (this is expected and not an error)`);
142+
return;
130143
}
144+
const errorMessage = getErrorMessage(error);
145+
core.error(`Failed to add reaction: ${errorMessage}`);
146+
core.setFailed(`${ERR_API}: Failed to add reaction: ${errorMessage}`);
131147
}
132148

133149
/**

setup/js/handle_agent_failure.cjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,10 @@ function buildEngineFailureContext() {
700700
const agentOutputFile = process.env.GH_AW_AGENT_OUTPUT;
701701
const stdioLogPath = agentOutputFile ? path.join(path.dirname(agentOutputFile), "agent-stdio.log") : "/tmp/gh-aw/agent-stdio.log";
702702

703+
// Include engine ID in failure messages when available (e.g. "copilot", "claude", "codex")
704+
const engineId = process.env.GH_AW_ENGINE_ID || "";
705+
const engineLabel = engineId ? ` \`${engineId}\`` : " AI";
706+
703707
try {
704708
if (!fs.existsSync(stdioLogPath)) {
705709
core.info(`agent-stdio.log not found at ${stdioLogPath}, skipping engine failure context`);
@@ -753,7 +757,7 @@ function buildEngineFailureContext() {
753757
if (errorMessages.size > 0) {
754758
core.info(`Found ${errorMessages.size} engine error message(s) in agent-stdio.log`);
755759

756-
let context = "\n**⚠️ Engine Failure**: The AI engine terminated before producing output.\n\n**Error details:**\n";
760+
let context = `\n**⚠️ Engine Failure**: The${engineLabel} engine terminated before producing output.\n\n**Error details:**\n`;
757761
for (const message of errorMessages) {
758762
context += `- ${message}\n`;
759763
}
@@ -772,7 +776,7 @@ function buildEngineFailureContext() {
772776
const tailLines = nonEmptyLines.slice(-TAIL_LINES);
773777
core.info(`No specific error patterns found; including last ${tailLines.length} line(s) of agent-stdio.log as fallback`);
774778

775-
let context = "\n**⚠️ Engine Failure**: The AI engine terminated unexpectedly.\n\n**Last agent output:**\n```\n";
779+
let context = `\n**⚠️ Engine Failure**: The${engineLabel} engine terminated unexpectedly.\n\n**Last agent output:**\n\`\`\`\n`;
776780
context += tailLines.join("\n");
777781
context += "\n```\n\n";
778782
return context;

setup/js/sanitize_content_core.cjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,8 +931,10 @@ function hardenUnicodeText(text) {
931931

932932
// Step 3: Strip invisible zero-width characters that can hide content
933933
// These include: zero-width space, zero-width non-joiner, zero-width joiner,
934+
// left-to-right mark (U+200E), right-to-left mark (U+200F),
935+
// soft hyphen (U+00AD), combining grapheme joiner (U+034F),
934936
// word joiner, and byte order mark
935-
result = result.replace(/[\u200B\u200C\u200D\u2060\uFEFF]/g, "");
937+
result = result.replace(/[\u00AD\u034F\u200B\u200C\u200D\u200E\u200F\u2060\uFEFF]/g, "");
936938

937939
// Step 4: Remove bidirectional text override controls
938940
// These can be used to reverse text direction and create visual spoofs

setup/md/manifest_protection_create_pr_fallback.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
> This was originally intended as a pull request, but the patch modifies protected files. These files may affect project dependencies, CI/CD pipelines, or agent behaviour. **Please review the changes carefully** before creating the pull request.
99
>
1010
> **[Click here to create the pull request once you have reviewed the changes]({create_pr_url})**
11-
12-
<details>
13-
<summary>Protected files</summary>
14-
15-
{files}
16-
17-
</details>
11+
>
12+
> <details>
13+
> <summary>Protected files</summary>
14+
>
15+
> {files}
16+
>
17+
> </details>
1818
1919
To route changes like this to a review issue instead of blocking, configure `protected-files: fallback-to-issue` in your workflow configuration.
2020

setup/md/manifest_protection_push_failed_fallback.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
> **Protected Files — Push Permission Denied**
77
>
88
> This was originally intended as a pull request, but the patch modifies protected files. A human must create the pull request manually.
9-
10-
<details>
11-
<summary>Protected files</summary>
12-
13-
{files}
14-
15-
The push was rejected because GitHub Actions does not have `workflows` permission to push these changes, and is never allowed to make such changes, or other authorization being used does not have this permission.
16-
17-
</details>
9+
>
10+
> <details>
11+
> <summary>Protected files</summary>
12+
>
13+
> {files}
14+
>
15+
> The push was rejected because GitHub Actions does not have `workflows` permission to push these changes, and is never allowed to make such changes, or other authorization being used does not have this permission.
16+
>
17+
> </details>
1818
1919
<details>
2020
<summary><b>Create the pull request manually</b></summary>

setup/md/manifest_protection_push_to_pr_fallback.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
> **Target Pull Request:** [#{pull_number}]({pr_url})
77
>
88
> **Please review the changes carefully** before pushing them to the pull request branch. These files may affect project dependencies, CI/CD pipelines, or agent behaviour.
9-
10-
<details>
11-
<summary>Protected files</summary>
12-
13-
{files}
14-
15-
</details>
9+
>
10+
> <details>
11+
> <summary>Protected files</summary>
12+
>
13+
> {files}
14+
>
15+
> </details>
1616
1717
---
1818

0 commit comments

Comments
 (0)