Skip to content

Commit f8495a6

Browse files
chore: sync actions from gh-aw@v0.71.4 (#89)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 07c7335 commit f8495a6

75 files changed

Lines changed: 5520 additions & 620 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

setup/js/action_conclusion_otlp.cjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const { getActionInput } = require("./action_input_utils.cjs");
5050
* @returns {Promise<void>}
5151
*/
5252
async function run() {
53-
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
53+
const endpoints = process.env.GH_AW_OTLP_ENDPOINTS;
5454

5555
// Read the job-start timestamp written by action_setup_otlp so the conclusion
5656
// span duration covers the actual job execution window, not just this step's overhead.
@@ -60,15 +60,15 @@ async function run() {
6060
const jobName = getActionInput("JOB_NAME");
6161
const spanName = jobName ? `gh-aw.${jobName}.conclusion` : "gh-aw.job.conclusion";
6262

63-
if (!endpoint) {
64-
console.log("[otlp] OTEL_EXPORTER_OTLP_ENDPOINT not set, skipping OTLP export (will attempt JSONL mirror)");
63+
if (!endpoints) {
64+
console.log("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)");
6565
} else {
66-
console.log(`[otlp] sending conclusion span "${spanName}" to ${endpoint}`);
66+
console.log(`[otlp] sending conclusion span "${spanName}" to configured endpoints`);
6767
}
6868

6969
await sendOtlpSpan.sendJobConclusionSpan(spanName, { startMs });
7070

71-
if (endpoint) {
71+
if (endpoints) {
7272
console.log(`[otlp] conclusion span export attempted`);
7373
}
7474
}

setup/js/action_setup_otlp.cjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const { getActionInput } = require("./action_input_utils.cjs");
4040
* @returns {Promise<void>}
4141
*/
4242
async function run() {
43-
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
43+
const endpoints = process.env.GH_AW_OTLP_ENDPOINTS;
4444

4545
const { sendJobSetupSpan, isValidTraceId, isValidSpanId } = require(path.join(__dirname, "send_otlp_span.cjs"));
4646

@@ -65,17 +65,17 @@ async function run() {
6565
process.env.INPUT_JOB_NAME = inputJobName;
6666
}
6767

68-
if (!endpoint) {
69-
console.log("[otlp] OTEL_EXPORTER_OTLP_ENDPOINT not set, skipping setup span");
68+
if (!endpoints) {
69+
console.log("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span");
7070
} else {
71-
console.log(`[otlp] sending setup span to ${endpoint}`);
71+
console.log(`[otlp] sending setup span to configured endpoints`);
7272
}
7373

7474
const { traceId, spanId } = await sendJobSetupSpan({ startMs, traceId: inputTraceId || undefined });
7575

7676
console.log(`[otlp] resolved trace-id=${traceId}`);
7777

78-
if (endpoint) {
78+
if (endpoints) {
7979
console.log(`[otlp] setup span sent (traceId=${traceId}, spanId=${spanId})`);
8080
}
8181

setup/js/add_comment.cjs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_help
1717
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
1818
const { getMissingInfoSections } = require("./missing_messages_helper.cjs");
1919
const { getMessages } = require("./messages_core.cjs");
20+
const { getBodyHeader } = require("./messages_header.cjs");
2021
const { sanitizeContent } = require("./sanitize_content.cjs");
2122
const { MAX_COMMENT_LENGTH, MAX_MENTIONS, MAX_LINKS, enforceCommentLimits } = require("./comment_limit_helpers.cjs");
2223
const { resolveTopLevelDiscussionCommentId } = require("./github_api_helpers.cjs");
@@ -582,9 +583,15 @@ async function main(config = {}) {
582583

583584
// Inject CAUTION at top of body if threat detection warning was raised
584585
const detectionCaution = getDetectionCautionAlert(workflowName, runUrl);
585-
if (detectionCaution) {
586-
processedBody = detectionCaution + "\n\n" + processedBody;
587-
}
586+
587+
// Inject body header if configured (placed after caution, before user content)
588+
const bodyHeader = getBodyHeader({ workflowName, runUrl });
589+
590+
// Build prefix: caution (if any) → body header (if any) → user content
591+
let prefix = "";
592+
if (detectionCaution) prefix += detectionCaution + "\n\n";
593+
if (bodyHeader) prefix += bodyHeader + "\n\n";
594+
if (prefix) processedBody = prefix + processedBody;
588595

589596
// Add tracker ID and footer
590597
const trackerIDComment = getTrackerID("markdown");
@@ -657,7 +664,7 @@ async function main(config = {}) {
657664
// Records a created comment in createdComments and returns the success result.
658665
const recordComment = (/** @type {{ id: string | number, html_url: string }} */ comment, /** @type {boolean} */ isDiscussionFlag) => {
659666
createdComments.push({ id: comment.id, html_url: comment.html_url, _tracking: { commentId: comment.id, itemNumber, repo: itemRepo, isDiscussion: isDiscussionFlag } });
660-
return { success: true, commentId: comment.id, url: comment.html_url, itemNumber, repo: itemRepo, isDiscussion: isDiscussionFlag };
667+
return { success: true, commentId: comment.id, url: comment.html_url, body: processedBody, itemNumber, repo: itemRepo, isDiscussion: isDiscussionFlag };
661668
};
662669

663670
// Normalize reply_to_id once so both the main discussion path and the

setup/js/add_labels.cjs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
2323
const { resolveRepoIssueTarget, loadTemporaryIdMapFromResolved } = require("./temporary_id.cjs");
2424
const { MAX_LABELS } = require("./constants.cjs");
2525
const { createCountGatedHandler } = require("./handler_scaffold.cjs");
26+
const { withRetry, RATE_LIMIT_RETRY_CONFIG } = require("./error_recovery.cjs");
2627

2728
/**
2829
* Main handler factory for add_labels
@@ -167,12 +168,17 @@ const main = createCountGatedHandler({
167168
}
168169

169170
try {
170-
await githubClient.rest.issues.addLabels({
171-
owner: repoParts.owner,
172-
repo: repoParts.repo,
173-
issue_number: itemNumber,
174-
labels: uniqueLabels,
175-
});
171+
await withRetry(
172+
() =>
173+
githubClient.rest.issues.addLabels({
174+
owner: repoParts.owner,
175+
repo: repoParts.repo,
176+
issue_number: itemNumber,
177+
labels: uniqueLabels,
178+
}),
179+
RATE_LIMIT_RETRY_CONFIG,
180+
`add_labels to ${contextType} #${itemNumber} in ${itemRepo}`
181+
);
176182

177183
core.info(`Successfully added ${uniqueLabels.length} labels to ${contextType} #${itemNumber} in ${itemRepo}`);
178184
return {

setup/js/add_reaction_and_edit_comment.cjs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,21 +185,16 @@ async function main() {
185185
* @param {string} reaction - The reaction type to add
186186
*/
187187
async function addReaction(endpoint, reaction) {
188-
const response = await github.request("POST " + endpoint, {
188+
const response = await github.request(`POST ${endpoint}`, {
189189
content: reaction,
190190
headers: {
191191
Accept: "application/vnd.github+json",
192192
},
193193
});
194194

195195
const reactionId = response.data?.id;
196-
if (reactionId) {
197-
core.info(`Successfully added reaction: ${reaction} (id: ${reactionId})`);
198-
core.setOutput("reaction-id", reactionId.toString());
199-
} else {
200-
core.info(`Successfully added reaction: ${reaction}`);
201-
core.setOutput("reaction-id", "");
202-
}
196+
core.info(`Successfully added reaction: ${reaction}${reactionId ? ` (id: ${reactionId})` : ""}`);
197+
core.setOutput("reaction-id", reactionId?.toString() ?? "");
203198
}
204199

205200
/**
@@ -369,7 +364,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName, invocatio
369364
}
370365

371366
// Create a new comment for non-discussion events
372-
const createResponse = await github.request("POST " + endpoint, {
367+
const createResponse = await github.request(`POST ${endpoint}`, {
373368
body: commentBody,
374369
headers: {
375370
Accept: "application/vnd.github+json",
@@ -380,7 +375,7 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName, invocatio
380375
} catch (error) {
381376
// Don't fail the entire job if comment creation fails - just log it
382377
const errorMessage = getErrorMessage(error);
383-
core.warning("Failed to create comment with workflow link (This is not critical - the reaction was still added successfully): " + errorMessage);
378+
core.warning(`Failed to create comment with workflow link (This is not critical - the reaction was still added successfully): ${errorMessage}`);
384379
}
385380
}
386381

setup/js/aw_context.cjs

Lines changed: 147 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// @ts-check
22
/// <reference types="@actions/github-script" />
33

4+
const { readExperimentAssignments } = require("./experiment_helpers.cjs");
5+
46
/**
57
* Resolves the item type, item number, and comment id from the GitHub Actions
68
* event payload, covering issues, pull requests, discussions, check runs,
@@ -88,6 +90,75 @@ function resolveItemContext(payload) {
8890
return { item_type: "", item_number: "", comment_id: "", comment_node_id: "" };
8991
}
9092

93+
/**
94+
* Builds a workflow-call identifier for the current workflow invocation.
95+
*
96+
* GitHub reusable workflows share the same run ID as their caller, so the
97+
* workflow ref is appended when available to distinguish parent and child
98+
* workflow invocations inside a single run.
99+
*
100+
* @param {string | number | null | undefined} runId
101+
* @param {string | number | null | undefined} runAttempt
102+
* @param {string | null | undefined} workflowRef
103+
* @returns {string}
104+
*/
105+
function buildWorkflowCallId(runId, runAttempt, workflowRef) {
106+
const normalizedRunId = String(runId ?? "").trim();
107+
if (!normalizedRunId) {
108+
return "";
109+
}
110+
111+
const normalizedRunAttempt = String(runAttempt ?? "").trim() || "1";
112+
const normalizedWorkflowRef = typeof workflowRef === "string" ? workflowRef.trim() : "";
113+
const baseId = `${normalizedRunId}-${normalizedRunAttempt}`;
114+
115+
return normalizedWorkflowRef ? `${baseId}:${normalizedWorkflowRef}` : baseId;
116+
}
117+
118+
/**
119+
* Parse inbound aw_context from workflow inputs or repository_dispatch payload.
120+
*
121+
* Callers may deliver aw_context as a JSON string (workflow_call/workflow_dispatch)
122+
* or as a plain object (repository_dispatch client_payload).
123+
*
124+
* @param {unknown} raw
125+
* @returns {Record<string, unknown> | null}
126+
*/
127+
function parseInboundAwContext(raw) {
128+
if (raw == null) {
129+
return null;
130+
}
131+
if (typeof raw === "string") {
132+
const trimmed = raw.trim();
133+
if (!trimmed) {
134+
return null;
135+
}
136+
try {
137+
const parsed = JSON.parse(trimmed);
138+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
139+
return parsed;
140+
}
141+
} catch {
142+
return null;
143+
}
144+
return null;
145+
}
146+
if (typeof raw === "object" && !Array.isArray(raw)) {
147+
return /** @type {Record<string, unknown>} */ raw;
148+
}
149+
return null;
150+
}
151+
152+
/**
153+
* Resolve inbound aw_context from the current GitHub payload, if any.
154+
*
155+
* @param {object | null | undefined} payload
156+
* @returns {Record<string, unknown> | null}
157+
*/
158+
function readInboundAwContext(payload) {
159+
return parseInboundAwContext(payload?.inputs?.aw_context) || parseInboundAwContext(payload?.client_payload?.aw_context);
160+
}
161+
91162
/**
92163
* Builds the aw_context object that identifies the calling workflow run.
93164
* This metadata is injected into dispatched workflows that declare an
@@ -98,7 +169,15 @@ function resolveItemContext(payload) {
98169
* @returns {{
99170
* repo: string,
100171
* run_id: string,
172+
* run_attempt: string,
101173
* workflow_id: string,
174+
* episode_id: string,
175+
* hop_id: string,
176+
* parent_hop_id: string,
177+
* origin_event: string,
178+
* root_repo: string,
179+
* root_workflow_id: string,
180+
* root_run_id: string,
102181
* workflow_call_id: string,
103182
* time: string,
104183
* actor: string,
@@ -111,7 +190,8 @@ function resolveItemContext(payload) {
111190
* workflow_run_conclusion: string,
112191
* otel_trace_id: string,
113192
* otel_parent_span_id: string,
114-
* trigger_label: string
193+
* trigger_label: string,
194+
* experiments: string
115195
* }}
116196
* Properties:
117197
* - item_type: Kind of entity that triggered the workflow (issue, pull_request,
@@ -144,19 +224,74 @@ function resolveItemContext(payload) {
144224
* - trigger_label: Name of the label that triggered the workflow for labeled/unlabeled
145225
* events (e.g. pull_request_target, issues, pull_request with labeled type).
146226
* Empty string for events that do not carry label information.
227+
* - experiments: Compact JSON string of the experiment variant assignments picked by
228+
* pick_experiment.cjs for the current workflow run (e.g. `{"caveman":"yes"}`).
229+
* Empty string when no experiments are declared or the assignments file cannot be read.
230+
* Propagated to dispatched child workflows so they can identify which variants the
231+
* parent workflow was running.
147232
*/
148233
function buildAwContext() {
149234
const { item_type, item_number, comment_id, comment_node_id } = resolveItemContext(context.payload);
235+
const workflowRef = process.env.GITHUB_WORKFLOW_REF ?? "";
236+
const currentRepo = `${context.repo.owner}/${context.repo.repo}`;
237+
const currentRunId = String(process.env.GITHUB_RUN_ID ?? context.runId ?? "");
238+
const currentRunAttempt = String(process.env.GITHUB_RUN_ATTEMPT ?? "1");
239+
const currentHopId = buildWorkflowCallId(currentRunId, currentRunAttempt, workflowRef);
240+
const inheritedContext = readInboundAwContext(context.payload);
241+
const inheritedHopId = typeof inheritedContext?.hop_id === "string" ? inheritedContext.hop_id.trim() : typeof inheritedContext?.workflow_call_id === "string" ? inheritedContext.workflow_call_id.trim() : "";
242+
const parentHopId = typeof inheritedContext?.parent_hop_id === "string" && inheritedContext.parent_hop_id.trim() ? inheritedContext.parent_hop_id.trim() : inheritedHopId;
243+
const episodeId = typeof inheritedContext?.episode_id === "string" && inheritedContext.episode_id.trim() ? inheritedContext.episode_id.trim() : inheritedHopId || currentHopId;
244+
const originEvent =
245+
typeof inheritedContext?.origin_event === "string" && inheritedContext.origin_event.trim()
246+
? inheritedContext.origin_event.trim()
247+
: typeof inheritedContext?.event_type === "string" && inheritedContext.event_type.trim()
248+
? inheritedContext.event_type.trim()
249+
: (context.eventName ?? "");
250+
const rootRepo =
251+
typeof inheritedContext?.root_repo === "string" && inheritedContext.root_repo.trim()
252+
? inheritedContext.root_repo.trim()
253+
: typeof inheritedContext?.repo === "string" && inheritedContext.repo.trim()
254+
? inheritedContext.repo.trim()
255+
: currentRepo;
256+
const rootWorkflowId =
257+
typeof inheritedContext?.root_workflow_id === "string" && inheritedContext.root_workflow_id.trim()
258+
? inheritedContext.root_workflow_id.trim()
259+
: typeof inheritedContext?.workflow_id === "string" && inheritedContext.workflow_id.trim()
260+
? inheritedContext.workflow_id.trim()
261+
: workflowRef;
262+
const rootRunId =
263+
typeof inheritedContext?.root_run_id === "string" && inheritedContext.root_run_id.trim()
264+
? inheritedContext.root_run_id.trim()
265+
: typeof inheritedContext?.run_id === "string" && inheritedContext.run_id.trim()
266+
? inheritedContext.run_id.trim()
267+
: currentRunId;
268+
const assignments = readExperimentAssignments();
269+
const experimentAssignments = assignments ? JSON.stringify(assignments) : "";
150270

151271
return {
152-
repo: `${context.repo.owner}/${context.repo.repo}`,
272+
repo: currentRepo,
153273
run_id: String(context.runId ?? ""),
274+
run_attempt: currentRunAttempt,
154275
// GITHUB_WORKFLOW_REF provides the full workflow file path including the ref,
155276
// e.g. "owner/repo/.github/workflows/dispatcher.yml@refs/heads/main"
156-
workflow_id: process.env.GITHUB_WORKFLOW_REF ?? "",
157-
// workflow_call_id uniquely identifies this specific call attempt:
158-
// combine run_id with run_attempt (GITHUB_RUN_ATTEMPT) so re-runs produce different IDs.
159-
workflow_call_id: `${process.env.GITHUB_RUN_ID ?? context.runId ?? ""}-${process.env.GITHUB_RUN_ATTEMPT ?? "1"}`,
277+
workflow_id: workflowRef,
278+
// episode_id identifies the full automation session across workflow hops.
279+
episode_id: episodeId,
280+
// hop_id uniquely identifies this specific workflow invocation.
281+
hop_id: currentHopId,
282+
// parent_hop_id identifies the immediate caller when a workflow was spawned
283+
// by a previous automation hop.
284+
parent_hop_id: parentHopId,
285+
// origin_event captures the original GitHub event that started the episode.
286+
origin_event: originEvent,
287+
// root_* fields stay stable across all child workflow hops in the episode.
288+
root_repo: rootRepo,
289+
root_workflow_id: rootWorkflowId,
290+
root_run_id: rootRunId,
291+
// workflow_call_id uniquely identifies this specific workflow invocation,
292+
// including the workflow file when GitHub reuses a single run for caller
293+
// and callee workflow_call executions. Kept as a legacy alias of hop_id.
294+
workflow_call_id: currentHopId,
160295
time: new Date().toISOString(),
161296
actor: context.actor ?? "",
162297
event_type: context.eventName ?? "",
@@ -184,7 +319,12 @@ function buildAwContext() {
184319
// issues, pull_request, etc.). Empty string for events without label data such as
185320
// workflow_dispatch, push, or schedule.
186321
trigger_label: context.payload?.label?.name ?? "",
322+
// experiments is a compact JSON string of the A/B experiment variant assignments
323+
// picked by pick_experiment.cjs for the current workflow run (e.g. {"caveman":"yes"}).
324+
// Empty string when no experiments are declared or the assignments file cannot be read.
325+
// Propagated to dispatched child workflows for experiment context continuity.
326+
experiments: experimentAssignments,
187327
};
188328
}
189329

190-
module.exports = { buildAwContext, resolveItemContext };
330+
module.exports = { buildAwContext, buildWorkflowCallId, resolveItemContext };

0 commit comments

Comments
 (0)