Skip to content

Commit 23453ec

Browse files
chore: sync actions from gh-aw@v0.74.2 (#101)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent b07cf98 commit 23453ec

11 files changed

Lines changed: 1183 additions & 138 deletions

setup/js/action_conclusion_otlp.cjs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,37 @@
4444
const sendOtlpSpan = require("./send_otlp_span.cjs");
4545
const { getActionInput } = require("./action_input_utils.cjs");
4646

47+
/**
48+
* Build the OTLP span name from an optional job name.
49+
* @param {string | undefined} jobName
50+
* @returns {string}
51+
*/
52+
function buildSpanName(jobName) {
53+
return jobName ? `gh-aw.${jobName}.conclusion` : "gh-aw.job.conclusion";
54+
}
55+
56+
/**
57+
* Parse a positive finite epoch-ms value from an env var string.
58+
* Returns undefined when the string is absent, non-numeric, zero, or negative.
59+
* @param {string | undefined} raw
60+
* @returns {number | undefined}
61+
*/
62+
function parseJobStartMs(raw) {
63+
const ms = Number(raw);
64+
return Number.isFinite(ms) && ms > 0 ? ms : undefined;
65+
}
66+
4767
/**
4868
* Send the OTLP job-conclusion span. Non-fatal: all errors are silently
4969
* swallowed.
5070
* @returns {Promise<void>}
5171
*/
5272
async function run() {
5373
const endpoints = process.env.GH_AW_OTLP_ENDPOINTS;
54-
55-
const jobName = getActionInput("JOB_NAME");
56-
const spanName = jobName ? `gh-aw.${jobName}.conclusion` : "gh-aw.job.conclusion";
57-
58-
// Read the job-start timestamp written by action_setup_otlp so the conclusion
59-
// span duration covers the actual job execution window, not just this step's overhead.
60-
const jobStartMs = Number(process.env.GITHUB_AW_OTEL_JOB_START_MS);
61-
const startMs = Number.isFinite(jobStartMs) && jobStartMs > 0 ? jobStartMs : undefined;
74+
const spanName = buildSpanName(getActionInput("JOB_NAME"));
75+
// Use the job-start timestamp set by action_setup_otlp so the conclusion span
76+
// duration covers the actual job execution window, not just this step's overhead.
77+
const startMs = parseJobStartMs(process.env.GITHUB_AW_OTEL_JOB_START_MS);
6278

6379
if (endpoints) {
6480
console.log(`[otlp] sending conclusion span "${spanName}" to configured endpoints`);
@@ -73,7 +89,7 @@ async function run() {
7389
}
7490
}
7591

76-
module.exports = { run };
92+
module.exports = { run, buildSpanName, parseJobStartMs };
7793

7894
// When invoked directly (node action_conclusion_otlp.cjs) from clean.sh,
7995
// run immediately. Non-fatal: errors are silently swallowed.

setup/js/check_membership.cjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,17 @@ async function main() {
3333
if (eventName === "workflow_dispatch") {
3434
const awContext = readWorkflowDispatchAwContext(context.payload);
3535
const commandName = typeof awContext?.command_name === "string" ? awContext.command_name.trim() : "";
36+
const triggerLabel = typeof awContext?.trigger_label === "string" ? awContext.trigger_label.trim() : "";
3637
const propagatedActor = typeof awContext?.actor === "string" ? awContext.actor.trim() : "";
3738

38-
if (commandName && actor === "github-actions[bot]") {
39+
if ((commandName || triggerLabel) && actor === "github-actions[bot]") {
3940
if (!propagatedActor) {
40-
const errorMessage = "Access denied: workflow_dispatch aw_context.actor is required for centralized slash-command dispatches.";
41+
const errorMessage = "Access denied: workflow_dispatch aw_context.actor is required for centralized command dispatches.";
4142
core.warning(errorMessage);
4243
core.setOutput("is_team_member", "false");
4344
core.setOutput("result", "config_error");
4445
core.setOutput("error_message", errorMessage);
45-
await writeDenialSummary(errorMessage, "Ensure centralized slash-command dispatches include aw_context.actor.");
46+
await writeDenialSummary(errorMessage, "Ensure centralized command dispatches include aw_context.actor.");
4647
return;
4748
}
4849

setup/js/create_agent_session.cjs

Lines changed: 61 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,36 @@ const { getBaseBranch } = require("./get_base_branch.cjs");
77
const { isStagedMode } = require("./safe_output_helpers.cjs");
88
const { generateStagedPreview } = require("./staged_preview.cjs");
99

10-
const fs = require("fs");
11-
const path = require("path");
12-
1310
/**
1411
* Module-level state — populated by handleMessage(), read by the exported getters below.
1512
* Using module-level variables (rather than closure-only state) allows the handler
1613
* manager to read final output values after all messages have been processed.
17-
* @type {Array<{number: string, url: string, success: boolean, error?: string}>}
14+
* @type {Array<{id: string, url: string, success: boolean, error?: string}>}
1815
*/
1916
let _allResults = [];
2017

18+
/**
19+
* Create a dedicated GitHub client for create-agent-session operations.
20+
*
21+
* Token precedence:
22+
* 1. config["github-token"] — per-handler PAT configured in the workflow frontmatter
23+
* 2. GH_AW_AGENT_SESSION_TOKEN — agent token injected by the compiler as a step env var
24+
* (evaluates to: GH_AW_AGENT_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN)
25+
* 3. global github — step-level token (fallback when no agent token is available)
26+
*
27+
* @param {Object} config - Handler configuration
28+
* @returns {Promise<Object>} Authenticated GitHub client
29+
*/
30+
async function createAgentSessionGitHubClient(config) {
31+
const token = config["github-token"] || process.env.GH_AW_AGENT_SESSION_TOKEN;
32+
if (!token) {
33+
core.debug("No dedicated agent token configured — using step-level github client for create-agent-session operations");
34+
return github;
35+
}
36+
core.info("Using dedicated github client for create-agent-session operations");
37+
return global.getOctokit(token);
38+
}
39+
2140
/**
2241
* Handler factory for create-agent-session safe output.
2342
*
@@ -41,17 +60,20 @@ async function main(config = {}) {
4160
core.info(`Default target repo: ${defaultTargetRepo}`);
4261
if (allowedRepos.size > 0) core.info(`Allowed repos: ${[...allowedRepos].join(", ")}`);
4362

63+
// Create a dedicated Octokit instance using the agent token
64+
const githubClient = await createAgentSessionGitHubClient(config);
65+
4466
/**
4567
* Process a single create_agent_session message.
4668
* @param {Object} message - The agent output message
47-
* @returns {Promise<{success: boolean, number?: string, url?: string, error?: string, skipped?: boolean}>}
69+
* @returns {Promise<{success: boolean, id?: string, url?: string, error?: string, skipped?: boolean}>}
4870
*/
4971
return async function handleMessage(message) {
5072
const taskDescription = message.body;
5173

5274
if (!taskDescription || taskDescription.trim() === "") {
5375
core.warning("Agent task description is empty, skipping");
54-
_allResults.push({ number: "", url: "", success: false, error: "Empty task description" });
76+
_allResults.push({ id: "", url: "", success: false, error: "Empty task description" });
5577
return { success: false, error: "Empty task description" };
5678
}
5779

@@ -60,7 +82,7 @@ async function main(config = {}) {
6082
if (!repoResult.success) {
6183
const errorMsg = `E004: ${repoResult.error}`;
6284
core.error(errorMsg);
63-
_allResults.push({ number: "", url: "", success: false, error: repoResult.error });
85+
_allResults.push({ id: "", url: "", success: false, error: repoResult.error });
6486
return { success: false, error: repoResult.error };
6587
}
6688
const { repo: effectiveRepo, repoParts } = repoResult;
@@ -87,91 +109,50 @@ async function main(config = {}) {
87109
}
88110

89111
try {
90-
// Write task description to a temporary file
91-
const tmpDir = "/tmp/gh-aw";
92-
if (!fs.existsSync(tmpDir)) {
93-
fs.mkdirSync(tmpDir, { recursive: true });
94-
}
95-
96-
const taskIndex = _allResults.length + 1;
97-
const taskFile = path.join(tmpDir, `agent-task-description-${taskIndex}.md`);
98-
fs.writeFileSync(taskFile, taskDescription, "utf8");
99-
core.info(`Task ${taskIndex}: Task description written to ${taskFile}`);
112+
core.info(`Task ${_allResults.length + 1}: Creating agent session in ${effectiveRepo} on branch ${baseBranch}`);
113+
114+
// Call the GitHub REST API to start a task
115+
// Reference: https://docs.github.com/en/rest/agent-tasks/agent-tasks?apiVersion=2026-03-10#start-a-task
116+
const response = await githubClient.request("POST /agents/repos/{owner}/{repo}/tasks", {
117+
owner: repoParts.owner,
118+
repo: repoParts.repo,
119+
prompt: taskDescription,
120+
base_ref: baseBranch,
121+
headers: { "X-GitHub-Api-Version": "2026-03-10" },
122+
});
100123

101-
// Build gh agent-task create command
102-
const ghArgs = ["agent-task", "create", "--from-file", taskFile, "--base", baseBranch];
124+
const task = response.data;
125+
const taskId = task.id || "";
126+
const taskUrl = task.html_url || task.url || "";
103127

104-
const contextRepo = `${context.repo.owner}/${context.repo.repo}`;
105-
if (effectiveRepo !== contextRepo) {
106-
ghArgs.push("--repo", effectiveRepo);
107-
}
108-
109-
core.info(`Task ${taskIndex}: Creating agent session with command: gh ${ghArgs.join(" ")}`);
110-
111-
// Determine token: prefer per-handler token, fall back to step-level token
112-
const ghToken = config["github-token"] || process.env.GH_AW_AGENT_SESSION_TOKEN || process.env.GITHUB_TOKEN || "";
113-
114-
// Execute gh agent-task create command
115-
let taskOutput;
116-
try {
117-
taskOutput = await exec.getExecOutput("gh", ghArgs, {
118-
silent: false,
119-
ignoreReturnCode: false,
120-
env: {
121-
...process.env,
122-
GH_TOKEN: ghToken,
123-
},
124-
});
125-
} catch (execError) {
126-
const errorMessage = execError instanceof Error ? execError.message : String(execError);
127-
128-
// Check for authentication/permission errors
129-
if (errorMessage.includes("authentication") || errorMessage.includes("permission") || errorMessage.includes("forbidden") || errorMessage.includes("401") || errorMessage.includes("403")) {
130-
core.error(`Task ${taskIndex}: Failed to create agent session due to authentication/permission error.`);
131-
core.error(`The default GITHUB_TOKEN may not have permission to create agent sessions.`);
132-
core.error(`Configure a Personal Access Token (PAT) using the handler's github-token setting or GH_AW_AGENT_SESSION_TOKEN.`);
133-
core.error(`See documentation: https://github.github.com/gh-aw/reference/safe-outputs/#agent-task-creation-create-agent-session`);
134-
} else {
135-
core.error(`Task ${taskIndex}: Failed to create agent session: ${errorMessage}`);
136-
}
137-
_allResults.push({ number: "", url: "", success: false, error: errorMessage });
138-
return { success: false, error: errorMessage };
139-
}
128+
core.info(`✅ Successfully created agent session ${taskId}`);
129+
_allResults.push({ id: taskId, url: taskUrl, success: true });
130+
return { success: true, id: taskId, url: taskUrl };
131+
} catch (error) {
132+
const errorMessage = getErrorMessage(error);
140133

141-
// Parse the output to extract task number and URL.
142-
// Expected output format from gh agent-task create is typically:
143-
// https://github.com/owner/repo/issues/123
144-
const output = taskOutput.stdout.trim();
145-
core.info(`Task ${taskIndex}: Agent task created: ${output}`);
146-
147-
// Extract task number from URL
148-
const urlMatch = output.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
149-
if (urlMatch) {
150-
const taskNumber = urlMatch[1];
151-
core.info(`✅ Successfully created agent session #${taskNumber}`);
152-
_allResults.push({ number: taskNumber, url: output, success: true });
153-
return { success: true, number: taskNumber, url: output };
134+
// Check for authentication/permission errors
135+
if (errorMessage.includes("authentication") || errorMessage.includes("permission") || errorMessage.includes("forbidden") || errorMessage.includes("401") || errorMessage.includes("403")) {
136+
core.error(`Failed to create agent session due to authentication/permission error.`);
137+
core.error(`The default GITHUB_TOKEN may not have permission to create agent sessions.`);
138+
core.error(`Configure a Personal Access Token (PAT) using the handler's github-token setting or GH_AW_AGENT_SESSION_TOKEN.`);
139+
core.error(`See documentation: https://github.github.com/gh-aw/reference/safe-outputs/#agent-task-creation-create-agent-session`);
154140
} else {
155-
core.warning(`Task ${taskIndex}: Could not parse task number from output: ${output}`);
156-
_allResults.push({ number: "", url: output, success: true });
157-
return { success: true, number: "", url: output };
141+
core.error(`Error creating agent session: ${errorMessage}`);
158142
}
159-
} catch (error) {
160-
const errorMessage = getErrorMessage(error);
161-
core.error(`Error creating agent session: ${errorMessage}`);
162-
_allResults.push({ number: "", url: "", success: false, error: errorMessage });
143+
_allResults.push({ id: "", url: "", success: false, error: errorMessage });
163144
return { success: false, error: errorMessage };
164145
}
165146
};
166147
}
167148

168149
/**
169-
* Returns the session_number output: the number of the first successfully created session.
150+
* Returns the session_number output: the ID of the first successfully created session.
170151
* @returns {string}
171152
*/
172153
function getCreateAgentSessionNumber() {
173-
const first = _allResults.find(r => r.success && r.number);
174-
return first ? first.number : "";
154+
const first = _allResults.find(r => r.success && r.id);
155+
return first ? first.id : "";
175156
}
176157

177158
/**
@@ -200,8 +181,8 @@ async function writeCreateAgentSessionSummary() {
200181
summaryContent += `✅ Successfully created ${successResults.length} agent session(s):\n\n`;
201182
summaryContent += successResults
202183
.map((r, i) => {
203-
if (r.url && r.number) {
204-
return `- [#${r.number}](${r.url})`;
184+
if (r.url && r.id) {
185+
return `- [${r.id}](${r.url})`;
205186
} else if (r.url) {
206187
return `- [Session ${i + 1}](${r.url})`;
207188
}

setup/js/create_pull_request.cjs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,13 +1265,33 @@ async function main(config = {}) {
12651265
// This preserves merge commit topology and per-commit metadata (messages, authorship)
12661266
// unlike git format-patch which flattens history and drops merge resolution content.
12671267
core.info(`Applying changes from bundle: ${bundleFilePath}`);
1268-
const bundleBranchRef = originalAgentBranch || branchName;
1268+
let bundleBranchRef = `refs/heads/${originalAgentBranch || branchName}`;
12691269
try {
12701270
await ensureFullHistoryForBundle(exec);
12711271

12721272
// Fetch from bundle: creates a local branch pointing to the bundle's tip commit.
1273-
// The bundle contains refs/heads/<bundleBranchRef> which was the agent's working branch.
1274-
await exec.exec("git", ["fetch", bundleFilePath, `refs/heads/${bundleBranchRef}:refs/heads/${branchName}`]);
1273+
// bundleBranchRef is the source ref inside the bundle (typically refs/heads/<agent-branch>).
1274+
try {
1275+
await exec.exec("git", ["fetch", bundleFilePath, `${bundleBranchRef}:refs/heads/${branchName}`]);
1276+
} catch (initialFetchError) {
1277+
// Fallback: resolve the source ref directly from the bundle contents.
1278+
// Some agents may emit a JSONL branch name that differs from the ref embedded in the bundle.
1279+
const initialFetchErrorMessage = initialFetchError instanceof Error ? initialFetchError.message : String(initialFetchError);
1280+
core.warning(`Bundle fetch with ${bundleBranchRef} failed: ${initialFetchErrorMessage}; resolving branch ref from bundle heads`);
1281+
const { stdout: bundleHeadsOutput } = await exec.getExecOutput("git", ["bundle", "list-heads", bundleFilePath]);
1282+
const branchRefs = bundleHeadsOutput
1283+
.split("\n")
1284+
.map(line => line.trim().split(/\s+/)[1] || "")
1285+
.filter(ref => /^refs\/heads\/[A-Za-z0-9._][A-Za-z0-9._/-]*$/.test(ref));
1286+
1287+
if (branchRefs.length === 1) {
1288+
bundleBranchRef = branchRefs[0];
1289+
core.info(`Resolved bundle source ref from list-heads: ${bundleBranchRef}`);
1290+
await exec.exec("git", ["fetch", bundleFilePath, `${bundleBranchRef}:refs/heads/${branchName}`]);
1291+
} else {
1292+
throw new Error(`Failed to resolve bundle branch ref from list-heads: expected exactly 1 refs/heads entry, found ${branchRefs.length}`, { cause: initialFetchError });
1293+
}
1294+
}
12751295
core.info(`Created local branch ${branchName} from bundle`);
12761296
await exec.exec("git", ["checkout", branchName]);
12771297
core.info(`Checked out branch ${branchName} from bundle`);
@@ -1334,7 +1354,7 @@ To create a pull request with the changes:
13341354
gh run download ${runId} -n agent -D /tmp/agent-${runId}
13351355
13361356
# Fetch the bundle into a local branch
1337-
git fetch /tmp/agent-${runId}/${artifactFileName} refs/heads/${bundleBranchRef}:refs/heads/${branchName}
1357+
git fetch /tmp/agent-${runId}/${artifactFileName} ${bundleBranchRef}:refs/heads/${branchName}
13381358
git checkout ${branchName}
13391359
13401360
# Push the branch to origin

0 commit comments

Comments
 (0)