Skip to content

Commit addd8a8

Browse files
chore: sync actions from gh-aw@v0.67.1 (#68)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 182c893 commit addd8a8

31 files changed

Lines changed: 998 additions & 69 deletions

setup/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ inputs:
1111
required: false
1212
default: 'false'
1313
job-name:
14-
description: 'Name of the job being set up. When OTEL_EXPORTER_OTLP_ENDPOINT is configured, a gh-aw.job.setup span is pushed to the OTLP endpoint.'
14+
description: 'Name of the job being set up. When OTEL_EXPORTER_OTLP_ENDPOINT is configured, a gh-aw.<job-name>.setup span is pushed to the OTLP endpoint.'
1515
required: false
1616
default: ''
1717
trace-id:
@@ -23,7 +23,7 @@ outputs:
2323
files_copied:
2424
description: 'Number of files copied'
2525
trace-id:
26-
description: 'The OTLP trace ID used for the gh-aw.job.setup span. Pass this to subsequent job setup steps via the trace-id input to correlate all job spans under a single trace.'
26+
description: 'The OTLP trace ID used for the gh-aw.<job-name>.setup span. Pass this to subsequent job setup steps via the trace-id input to correlate all job spans under a single trace.'
2727

2828
runs:
2929
using: 'node24'

setup/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,19 @@ const inputTraceId =
2323
process.env["INPUT_TRACE-ID"] ||
2424
"";
2525

26+
// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
27+
// and INPUT_JOB-NAME (hyphen, used by some runner versions).
28+
const inputJobName =
29+
process.env["INPUT_JOB_NAME"] ||
30+
process.env["INPUT_JOB-NAME"] ||
31+
"";
32+
2633
const result = spawnSync(path.join(__dirname, "setup.sh"), [], {
2734
stdio: "inherit",
2835
env: Object.assign({}, process.env, {
2936
INPUT_SAFE_OUTPUT_CUSTOM_TOKENS: safeOutputCustomTokens,
3037
INPUT_TRACE_ID: inputTraceId,
38+
INPUT_JOB_NAME: inputJobName,
3139
// Tell setup.sh to skip the OTLP span: in action mode index.js sends it
3240
// after setup.sh returns so that the startMs captured here is used.
3341
GH_AW_SKIP_SETUP_OTLP: "1",
@@ -43,7 +51,7 @@ if (result.status !== 0) {
4351
process.exit(result.status ?? 1);
4452
}
4553

46-
// Send a gh-aw.job.setup span to the OTLP endpoint when configured.
54+
// Send a gh-aw.<jobName>.setup span to the OTLP endpoint when configured.
4755
// Delegates to action_setup_otlp.cjs so that script mode (setup.sh) and
4856
// dev/release mode share the same implementation.
4957
// Explicitly set INPUT_TRACE_ID (normalized above) so action_setup_otlp.cjs
@@ -54,6 +62,7 @@ if (result.status !== 0) {
5462
try {
5563
process.env.SETUP_START_MS = String(setupStartMs);
5664
process.env.INPUT_TRACE_ID = inputTraceId;
65+
process.env.INPUT_JOB_NAME = inputJobName;
5766
const { run } = require(path.join(__dirname, "js", "action_setup_otlp.cjs"));
5867
await run();
5968
} catch {

setup/js/action_conclusion_otlp.cjs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
/**
55
* action_conclusion_otlp.cjs
66
*
7-
* Sends a gh-aw.job.conclusion OTLP span (or a span named after the current
8-
* job). Used by both:
7+
* Sends a `gh-aw.<jobName>.conclusion` OTLP span (or `gh-aw.job.conclusion`
8+
* when no job name is configured). Used by both:
99
*
1010
* - actions/setup/post.js (dev/release/action mode)
1111
* - actions/setup/clean.sh (script mode)
@@ -14,14 +14,22 @@
1414
*
1515
* Environment variables read:
1616
* INPUT_JOB_NAME – job name from the `job-name` action input; when set the
17-
* span is named "gh-aw.job.<name>", otherwise
17+
* span is named "gh-aw.<name>.conclusion", otherwise
1818
* "gh-aw.job.conclusion".
19+
* GH_AW_AGENT_CONCLUSION – agent job result passed from the agent job
20+
* ("success", "failure", "timed_out", etc.);
21+
* "failure" and "timed_out" set the span
22+
* status to STATUS_CODE_ERROR.
23+
* GITHUB_AW_OTEL_JOB_START_MS – epoch ms written by action_setup_otlp.cjs when
24+
* setup finished; used as the span startMs so the
25+
* conclusion span duration covers the actual job
26+
* execution window rather than this step's overhead.
1927
* GITHUB_AW_OTEL_TRACE_ID – parent trace ID (set by action_setup_otlp.cjs)
2028
* GITHUB_AW_OTEL_PARENT_SPAN_ID – parent span ID (set by action_setup_otlp.cjs)
2129
* OTEL_EXPORTER_OTLP_ENDPOINT – OTLP endpoint (no-op when not set)
2230
*/
2331

24-
const path = require("path");
32+
const sendOtlpSpan = require("./send_otlp_span.cjs");
2533

2634
/**
2735
* Send the OTLP job-conclusion span. Non-fatal: all errors are silently
@@ -35,11 +43,18 @@ async function run() {
3543
return;
3644
}
3745

38-
const spanName = process.env.INPUT_JOB_NAME ? `gh-aw.job.${process.env.INPUT_JOB_NAME}` : "gh-aw.job.conclusion";
46+
// Read the job-start timestamp written by action_setup_otlp so the conclusion
47+
// span duration covers the actual job execution window, not just this step's overhead.
48+
const rawJobStartMs = parseInt(process.env.GITHUB_AW_OTEL_JOB_START_MS || "0", 10);
49+
const startMs = rawJobStartMs > 0 ? rawJobStartMs : undefined;
50+
51+
// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
52+
// and INPUT_JOB-NAME (hyphen, used by some runner versions).
53+
const jobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
54+
const spanName = jobName ? `gh-aw.${jobName}.conclusion` : "gh-aw.job.conclusion";
3955
console.log(`[otlp] sending conclusion span "${spanName}" to ${endpoint}`);
4056

41-
const { sendJobConclusionSpan } = require(path.join(__dirname, "send_otlp_span.cjs"));
42-
await sendJobConclusionSpan(spanName);
57+
await sendOtlpSpan.sendJobConclusionSpan(spanName, { startMs });
4358
console.log(`[otlp] conclusion span sent`);
4459
}
4560

setup/js/action_setup_otlp.cjs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/**
55
* action_setup_otlp.cjs
66
*
7-
* Sends a gh-aw.job.setup OTLP span and writes the trace/span IDs to
7+
* Sends a `gh-aw.<jobName>.setup` OTLP span and writes the trace/span IDs to
88
* GITHUB_OUTPUT and GITHUB_ENV. Used by both:
99
*
1010
* - actions/setup/index.js (dev/release/action mode)
@@ -17,10 +17,17 @@
1717
* GITHUB_OUTPUT – path to the GitHub Actions output file
1818
* GITHUB_ENV – path to the GitHub Actions env file
1919
* INPUT_* – standard GitHub Actions input env vars (read by sendJobSetupSpan)
20+
*
21+
* Environment variables written:
22+
* GITHUB_AW_OTEL_TRACE_ID – resolved trace ID (for cross-job correlation)
23+
* GITHUB_AW_OTEL_PARENT_SPAN_ID – setup span ID (links conclusion span as child)
24+
* GITHUB_AW_OTEL_JOB_START_MS – epoch ms when setup finished (used by conclusion
25+
* span to measure actual job execution duration)
2026
*/
2127

2228
const path = require("path");
2329
const { appendFileSync } = require("fs");
30+
const { nowMs } = require("./performance_now.cjs");
2431

2532
/**
2633
* Send the OTLP job-setup span and propagate trace context via GITHUB_OUTPUT /
@@ -50,6 +57,18 @@ async function run() {
5057
console.log("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated");
5158
}
5259

60+
// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
61+
// and INPUT_JOB-NAME (hyphen, used by some runner versions). Mirror the same
62+
// two-key lookup that INPUT_TRACE_ID uses above so script-mode invocations
63+
// (setup.sh → node action_setup_otlp.cjs) always resolve the job name even
64+
// when the runner preserves the original hyphen in the env var name.
65+
const inputJobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
66+
if (inputJobName) {
67+
// Normalise to the canonical underscore form so sendJobSetupSpan (which
68+
// reads process.env.INPUT_JOB_NAME) always finds the value.
69+
process.env.INPUT_JOB_NAME = inputJobName;
70+
}
71+
5372
if (!endpoint) {
5473
console.log("[otlp] OTEL_EXPORTER_OTLP_ENDPOINT not set, skipping setup span");
5574
} else {
@@ -83,6 +102,11 @@ async function run() {
83102
appendFileSync(process.env.GITHUB_ENV, `GITHUB_AW_OTEL_PARENT_SPAN_ID=${spanId}\n`);
84103
console.log(`[otlp] GITHUB_AW_OTEL_PARENT_SPAN_ID written to GITHUB_ENV`);
85104
}
105+
// Propagate setup-end timestamp so the conclusion span can measure actual
106+
// job execution duration (setup-end → conclusion-start).
107+
const setupEndMs = Math.round(nowMs());
108+
appendFileSync(process.env.GITHUB_ENV, `GITHUB_AW_OTEL_JOB_START_MS=${setupEndMs}\n`);
109+
console.log(`[otlp] GITHUB_AW_OTEL_JOB_START_MS written to GITHUB_ENV`);
86110
}
87111
}
88112

setup/js/check_command_position.cjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/// <reference types="@actions/github-script" />
33

44
const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
5+
const { writeDenialSummary } = require("./pre_activation_summary.cjs");
56

67
/**
78
* Check if command is the first word in the triggering text
@@ -100,6 +101,10 @@ async function main() {
100101
core.warning(`⚠️ None of the commands [${expectedCommands}] matched the first word (found: '${firstWord}'). Workflow will be skipped.`);
101102
core.setOutput("command_position_ok", "false");
102103
core.setOutput("matched_command", "");
104+
await writeDenialSummary(
105+
`The trigger comment did not start with a required command. Expected one of: ${expectedCommands}. Found: \`${firstWord}\`.`,
106+
"Make sure the trigger comment starts with the required command defined in `on.command:` in the workflow frontmatter."
107+
);
103108
}
104109
} catch (error) {
105110
core.setFailed(`${ERR_API}: ${getErrorMessage(error)}`);

setup/js/check_membership.cjs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/// <reference types="@actions/github-script" />
33

44
const { parseRequiredPermissions, parseAllowedBots, checkRepositoryPermission, checkBotStatus, isAllowedBot } = require("./check_permissions_utils.cjs");
5+
const { writeDenialSummary } = require("./pre_activation_summary.cjs");
56

67
async function main() {
78
const { eventName } = context;
@@ -46,6 +47,7 @@ async function main() {
4647
core.setOutput("is_team_member", "false");
4748
core.setOutput("result", "config_error");
4849
core.setOutput("error_message", "Configuration error: Required permissions not specified");
50+
await writeDenialSummary("Configuration error: Required permissions not specified.", "Contact the repository administrator to fix the workflow frontmatter configuration.");
4951
return;
5052
}
5153

@@ -76,11 +78,13 @@ async function main() {
7678
core.setOutput("user_permission", "bot");
7779
return;
7880
} else if (botStatus.isBot && !botStatus.isActive) {
81+
const errorMessage = `Access denied: Bot '${actor}' is not active/installed on this repository`;
7982
core.warning(`Bot '${actor}' is in the allowed list but not active/installed on ${owner}/${repo}`);
8083
core.setOutput("is_team_member", "false");
8184
core.setOutput("result", "bot_not_active");
8285
core.setOutput("user_permission", result.permission ?? "bot");
83-
core.setOutput("error_message", `Access denied: Bot '${actor}' is not active/installed on this repository`);
86+
core.setOutput("error_message", errorMessage);
87+
await writeDenialSummary(errorMessage, "The bot is in the allowed list but is not installed or active on this repository. Install the GitHub App and try again.");
8488
return;
8589
} else {
8690
core.info(`Actor '${actor}' is in allowed bots list but bot status check failed`);
@@ -90,18 +94,20 @@ async function main() {
9094

9195
// Not authorized by role or bot
9296
if (result.error) {
97+
const errorMessage = `Repository permission check failed: ${result.error}`;
9398
core.setOutput("is_team_member", "false");
9499
core.setOutput("result", "api_error");
95-
core.setOutput("error_message", `Repository permission check failed: ${result.error}`);
100+
core.setOutput("error_message", errorMessage);
101+
await writeDenialSummary(errorMessage, "The permission check failed with a GitHub API error. Check the `pre_activation` job log for details.");
96102
} else {
103+
const errorMessage =
104+
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}. ` +
105+
`To allow this user to run the workflow, add their role to the frontmatter. Example: roles: [${requiredPermissions.join(", ")}, ${result.permission}]`;
97106
core.setOutput("is_team_member", "false");
98107
core.setOutput("result", "insufficient_permissions");
99108
core.setOutput("user_permission", result.permission);
100-
core.setOutput(
101-
"error_message",
102-
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}. ` +
103-
`To allow this user to run the workflow, add their role to the frontmatter. Example: roles: [${requiredPermissions.join(", ")}, ${result.permission}]`
104-
);
109+
core.setOutput("error_message", errorMessage);
110+
await writeDenialSummary(errorMessage, `To allow a bot or GitHub App actor, add it to \`on.bots:\` in the workflow frontmatter. ` + `To change the required roles for human actors, update \`on.roles:\` in the workflow frontmatter.`);
105111
}
106112
}
107113
}

setup/js/check_rate_limit.cjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/// <reference types="@actions/github-script" />
33

44
const { getErrorMessage } = require("./error_helpers.cjs");
5+
const { fetchAndLogRateLimit } = require("./github_rate_limit_logger.cjs");
56

67
/**
78
* Rate limit check for per-user per-workflow triggers
@@ -15,6 +16,9 @@ async function main() {
1516
const eventName = context.eventName;
1617
const runId = context.runId;
1718

19+
// Capture a rate-limit snapshot at the start of the check for observability.
20+
await fetchAndLogRateLimit(github, "check_rate_limit_start");
21+
1822
// Get workflow file name from GITHUB_WORKFLOW_REF (format: "owner/repo/.github/workflows/file.yml@ref")
1923
// or fall back to GITHUB_WORKFLOW (workflow name)
2024
const workflowRef = process.env.GITHUB_WORKFLOW_REF || "";
@@ -50,12 +54,13 @@ async function main() {
5054

5155
try {
5256
// Check user's permission level in the repository
53-
const { data: permissionData } = await github.rest.repos.getCollaboratorPermissionLevel({
57+
const permResponse = await github.rest.repos.getCollaboratorPermissionLevel({
5458
owner,
5559
repo,
5660
username: actor,
5761
});
5862

63+
const { data: permissionData } = permResponse;
5964
const userPermission = permissionData.permission;
6065
core.info(` User '${actor}' has permission level: ${userPermission}`);
6166

setup/js/check_skip_bots.cjs

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

4+
const { writeDenialSummary } = require("./pre_activation_summary.cjs");
5+
46
/**
57
* Check if the workflow should be skipped based on bot/user identity
68
* Reads skip-bots from GH_AW_SKIP_BOTS environment variable
@@ -48,10 +50,12 @@ async function main() {
4850

4951
if (isSkipped) {
5052
// User is in skip-bots, skip the workflow
53+
const errorMessage = `Workflow skipped: User '${actor}' is in skip-bots: [${skipBots.join(", ")}]`;
5154
core.info(`❌ User '${actor}' is in skip-bots [${skipBots.join(", ")}]. Workflow will be skipped.`);
5255
core.setOutput("skip_bots_ok", "false");
5356
core.setOutput("result", "skipped");
54-
core.setOutput("error_message", `Workflow skipped: User '${actor}' is in skip-bots: [${skipBots.join(", ")}]`);
57+
core.setOutput("error_message", errorMessage);
58+
await writeDenialSummary(errorMessage, "Update `on.skip-bots:` in the workflow frontmatter to change which bots are excluded.");
5559
} else {
5660
// User is NOT in skip-bots, allow workflow to proceed
5761
core.info(`✅ User '${actor}' is NOT in skip-bots [${skipBots.join(", ")}]. Workflow will proceed.`);

setup/js/check_skip_if_check_failing.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
const { getErrorMessage, isRateLimitError } = require("./error_helpers.cjs");
55
const { ERR_API } = require("./error_codes.cjs");
66
const { getBaseBranch } = require("./get_base_branch.cjs");
7+
const { writeDenialSummary } = require("./pre_activation_summary.cjs");
78

89
/**
910
* Determines the ref to check for CI status.
@@ -206,6 +207,7 @@ async function main() {
206207
const names = failingChecks.map(r => (r.status === "completed" ? `${r.name} (${r.conclusion})` : `${r.name} (${r.status})`)).join(", ");
207208
core.warning(`⚠️ Failing CI checks detected on "${ref}": ${names}. Workflow execution will be prevented by activation job.`);
208209
core.setOutput("skip_if_check_failing_ok", "false");
210+
await writeDenialSummary(`Failing CI checks detected on \`${ref}\`: ${names}.`, "Fix the failing check(s) referenced in `on.skip-if-check-failing:`, or update the frontmatter configuration.");
209211
return;
210212
}
211213

setup/js/check_skip_if_match.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
const { getErrorMessage } = require("./error_helpers.cjs");
55
const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs");
66
const { buildSearchQuery } = require("./check_skip_if_helpers.cjs");
7+
const { writeDenialSummary } = require("./pre_activation_summary.cjs");
78

89
async function main() {
910
const { GH_AW_SKIP_QUERY: skipQuery, GH_AW_WORKFLOW_NAME: workflowName, GH_AW_SKIP_MAX_MATCHES: maxMatchesStr = "1", GH_AW_SKIP_SCOPE: skipScope } = process.env;
@@ -42,6 +43,7 @@ async function main() {
4243
if (totalCount >= maxMatches) {
4344
core.warning(`🔍 Skip condition matched (${totalCount} items found, threshold: ${maxMatches}). Workflow execution will be prevented by activation job.`);
4445
core.setOutput("skip_check_ok", "false");
46+
await writeDenialSummary(`Skip-if-match query matched: ${totalCount} item(s) found (threshold: ${maxMatches}).`, "Update `on.skip-if-match:` in the workflow frontmatter if this skip was unexpected.");
4547
return;
4648
}
4749

0 commit comments

Comments
 (0)