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: 9 additions & 0 deletions actions/setup/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion actions/setup/js/action_conclusion_otlp.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ async function run() {
const rawJobStartMs = parseInt(process.env.GITHUB_AW_OTEL_JOB_START_MS || "0", 10);
const startMs = rawJobStartMs > 0 ? rawJobStartMs : undefined;

const spanName = process.env.INPUT_JOB_NAME ? `gh-aw.${process.env.INPUT_JOB_NAME}.conclusion` : "gh-aw.job.conclusion";
// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
// and INPUT_JOB-NAME (hyphen, used by some runner versions).
const jobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The lookup (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"]) occurs before trimming, so a whitespace-only INPUT_JOB_NAME will prevent the hyphen-form value from being used and will fall back to the default span name after .trim(). To make the fallback robust, trim both candidates first and pick the first non-empty trimmed value.

Suggested change
const jobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
const underscoreJobName = (process.env.INPUT_JOB_NAME || "").trim();
const hyphenJobName = (process.env["INPUT_JOB-NAME"] || "").trim();
const jobName = underscoreJobName || hyphenJobName;

Copilot uses AI. Check for mistakes.
const spanName = jobName ? `gh-aw.${jobName}.conclusion` : "gh-aw.job.conclusion";
console.log(`[otlp] sending conclusion span "${spanName}" to ${endpoint}`);

await sendOtlpSpan.sendJobConclusionSpan(spanName, { startMs });
Expand Down
16 changes: 16 additions & 0 deletions actions/setup/js/action_conclusion_otlp.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ describe("action_conclusion_otlp.cjs", () => {
originalEnv = {
OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
INPUT_JOB_NAME: process.env.INPUT_JOB_NAME,
"INPUT_JOB-NAME": process.env["INPUT_JOB-NAME"],
GITHUB_AW_OTEL_JOB_START_MS: process.env.GITHUB_AW_OTEL_JOB_START_MS,
};
delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
delete process.env.INPUT_JOB_NAME;
delete process.env["INPUT_JOB-NAME"];
delete process.env.GITHUB_AW_OTEL_JOB_START_MS;
});

Expand All @@ -49,6 +51,11 @@ describe("action_conclusion_otlp.cjs", () => {
} else {
delete process.env.INPUT_JOB_NAME;
}
if (originalEnv["INPUT_JOB-NAME"] !== undefined) {
process.env["INPUT_JOB-NAME"] = originalEnv["INPUT_JOB-NAME"];
} else {
delete process.env["INPUT_JOB-NAME"];
}
if (originalEnv.GITHUB_AW_OTEL_JOB_START_MS !== undefined) {
process.env.GITHUB_AW_OTEL_JOB_START_MS = originalEnv.GITHUB_AW_OTEL_JOB_START_MS;
} else {
Expand Down Expand Up @@ -113,6 +120,15 @@ describe("action_conclusion_otlp.cjs", () => {
expect(mockSendJobConclusionSpan).toHaveBeenCalledWith("gh-aw.agent.conclusion", { startMs: undefined });
});

it("should use job name from INPUT_JOB-NAME (hyphen form) when INPUT_JOB_NAME is not set", async () => {
delete process.env.INPUT_JOB_NAME;
process.env["INPUT_JOB-NAME"] = "agent";

await run();

expect(mockSendJobConclusionSpan).toHaveBeenCalledWith("gh-aw.agent.conclusion", { startMs: undefined });
});

it("should log the full span name in the sending message", async () => {
process.env.INPUT_JOB_NAME = "setup";

Expand Down
26 changes: 26 additions & 0 deletions actions/setup/js/action_otlp.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,32 @@ describe("action_setup_otlp run()", () => {
fetchSpy.mockRestore();
});

it("uses job name from INPUT_JOB-NAME (hyphen form) in setup span when INPUT_JOB_NAME is not set", async () => {
const tmpOut = path.join(path.dirname(__dirname), `action_setup_otlp_test_job_name_hyphen_${Date.now()}.txt`);
try {
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:14317";
process.env["INPUT_JOB-NAME"] = "agent";
delete process.env.INPUT_JOB_NAME;
process.env.GITHUB_OUTPUT = tmpOut;
process.env.GITHUB_ENV = tmpOut;

let capturedBody;
const fetchSpy = vi.spyOn(global, "fetch").mockImplementation((_url, opts) => {
capturedBody = opts?.body;
return Promise.resolve(new Response(null, { status: 200 }));
});

await runSetup();

const payload = JSON.parse(capturedBody);
const spanName = payload?.resourceSpans?.[0]?.scopeSpans?.[0]?.spans?.[0]?.name;
expect(spanName).toBe("gh-aw.agent.setup");
fetchSpy.mockRestore();
} finally {
Comment on lines +155 to +176
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This test restores fetchSpy only on the success path; if an assertion throws, the spy can leak into later tests (this file doesn’t have a global vi.restoreAllMocks() in afterEach). Move fetchSpy.mockRestore() into the finally block (or add an afterEach that restores mocks) to keep the suite isolated.

Copilot uses AI. Check for mistakes.
fs.rmSync(tmpOut, { force: true });
}
});

it("includes github.repository, github.run_id resource attributes in setup span", async () => {
const tmpOut = path.join(path.dirname(__dirname), `action_setup_otlp_test_resource_attrs_${Date.now()}.txt`);
try {
Expand Down
12 changes: 12 additions & 0 deletions actions/setup/js/action_setup_otlp.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ async function run() {
console.log("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated");
}

// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
// and INPUT_JOB-NAME (hyphen, used by some runner versions). Mirror the same
// two-key lookup that INPUT_TRACE_ID uses above so script-mode invocations
// (setup.sh → node action_setup_otlp.cjs) always resolve the job name even
// when the runner preserves the original hyphen in the env var name.
const inputJobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
if (inputJobName) {
// Normalise to the canonical underscore form so sendJobSetupSpan (which
// reads process.env.INPUT_JOB_NAME) always finds the value.
process.env.INPUT_JOB_NAME = inputJobName;
Comment on lines +65 to +69
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The fallback order uses process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] before trimming, so a whitespace-only INPUT_JOB_NAME will shadow a valid hyphen-form value and resolve to an empty string after .trim(). Consider trimming each candidate first and then selecting the first non-empty value; also delete/clear process.env.INPUT_JOB_NAME when the trimmed result is empty so downstream sendJobSetupSpan() doesn’t treat whitespace as a real job name.

Suggested change
const inputJobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
if (inputJobName) {
// Normalise to the canonical underscore form so sendJobSetupSpan (which
// reads process.env.INPUT_JOB_NAME) always finds the value.
process.env.INPUT_JOB_NAME = inputJobName;
const inputJobNameUnderscore = (process.env.INPUT_JOB_NAME || "").trim();
const inputJobNameHyphen = (process.env["INPUT_JOB-NAME"] || "").trim();
const inputJobName = inputJobNameUnderscore || inputJobNameHyphen;
if (inputJobName) {
// Normalise to the canonical underscore form so sendJobSetupSpan (which
// reads process.env.INPUT_JOB_NAME) always finds the value.
process.env.INPUT_JOB_NAME = inputJobName;
} else if (process.env.INPUT_JOB_NAME && !inputJobNameUnderscore) {
// Clear whitespace-only underscore-form input so downstream consumers do
// not treat it as a real job name.
delete process.env.INPUT_JOB_NAME;

Copilot uses AI. Check for mistakes.
}

if (!endpoint) {
console.log("[otlp] OTEL_EXPORTER_OTLP_ENDPOINT not set, skipping setup span");
} else {
Expand Down
2 changes: 1 addition & 1 deletion actions/setup/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ fi
# Skipped when GH_AW_SKIP_SETUP_OTLP=1 because index.js will send the span itself.
if [ -z "${GH_AW_SKIP_SETUP_OTLP}" ] && command -v node &>/dev/null && [ -f "${DESTINATION}/action_setup_otlp.cjs" ]; then
debug_log "Sending OTLP setup span..."
SETUP_START_MS="${SETUP_START_MS}" INPUT_TRACE_ID="${INPUT_TRACE_ID:-}" node "${DESTINATION}/action_setup_otlp.cjs" || true
SETUP_START_MS="${SETUP_START_MS}" INPUT_TRACE_ID="${INPUT_TRACE_ID:-}" INPUT_JOB_NAME="${INPUT_JOB_NAME:-}" node "${DESTINATION}/action_setup_otlp.cjs" || true
debug_log "OTLP setup span step complete"
fi

Expand Down
Loading