Repository navigation
Fix operational-value grader created-at acquisition and preserve grader result contract in gh aw logs
#60635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
d6b67f7
f03c927
13eec1b
f8335a2
2768b1b
1a4070c
8bde3b0
c25ee41
301b8b9
42b1018
ea8d5f3
01ead44
ad5eac0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,8 +9,50 @@ const { validateContextVariables } = require("./validate_context_variables.cjs") | |
| const validateLockdownRequirements = require("./validate_lockdown_requirements.cjs"); | ||
| const { writeMergedModelsJSON } = require("./merge_frontmatter_models.cjs"); | ||
| const { getErrorMessage } = require("./error_helpers.cjs"); | ||
| const { normalizeRunCreatedAt } = require("./run_created_at.cjs"); | ||
| const { ERR_CONFIG, ERR_SYSTEM } = require("./error_codes.cjs"); | ||
|
|
||
| // Retries guard the operational-value grader against transient run-metadata lookup failures. | ||
| const RUN_CREATED_AT_ATTEMPTS = 3; | ||
| const RUN_CREATED_AT_RETRY_DELAY_MS = 500; | ||
|
|
||
| /** | ||
| * Resolve the authoritative workflow-run creation time as a UTC ISO timestamp. | ||
| * Consumers (the operational-value grader) require a valid timestamp, so transient | ||
| * lookup failures are retried before the value is reported as unavailable. | ||
| * | ||
| * @param {typeof import('@actions/core')} core - GitHub Actions core library | ||
| * @param {any} ctx - GitHub Actions context object | ||
| * @param {any} [githubClient] - Authenticated GitHub client; falls back to global.github | ||
| * @returns {Promise<string>} normalized timestamp, or "" when it cannot be resolved | ||
| */ | ||
| async function resolveRunCreatedAt(core, ctx, githubClient) { | ||
| let lastError = "workflow run creation time is unavailable"; | ||
| for (let attempt = 1; attempt <= RUN_CREATED_AT_ATTEMPTS; attempt++) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot use withRetry
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switched to |
||
| try { | ||
| // @ts-ignore - global.github is set by setupGlobals() from github-script context | ||
| const github = githubClient || global.github; | ||
| const response = await github.rest.actions.getWorkflowRun({ | ||
| owner: ctx.repo.owner, | ||
| repo: ctx.repo.repo, | ||
| run_id: ctx.runId, | ||
| }); | ||
| const runCreatedAt = normalizeRunCreatedAt(response?.data?.created_at); | ||
| if (runCreatedAt) { | ||
| return runCreatedAt; | ||
| } | ||
| lastError = `workflow run creation time is not a valid timestamp: ${JSON.stringify(response?.data?.created_at)}`; | ||
| } catch (err) { | ||
| lastError = getErrorMessage(err); | ||
| } | ||
| if (attempt < RUN_CREATED_AT_ATTEMPTS) { | ||
| await new Promise(resolve => setTimeout(resolve, RUN_CREATED_AT_RETRY_DELAY_MS)); | ||
| } | ||
| } | ||
| core.warning(`Unable to load workflow-run creation time: ${lastError}`); | ||
| return ""; | ||
| } | ||
|
|
||
| /** | ||
| * Generate aw_info.json with workflow run metadata. | ||
| * Reads compile-time values from environment variables (GH_AW_INFO_*) and | ||
|
|
@@ -99,20 +141,7 @@ async function main(core, ctx, githubClient) { | |
| }; | ||
|
|
||
| if (process.env.GH_AW_INFO_FETCH_RUN_CREATED_AT === "true") { | ||
| try { | ||
| // @ts-ignore - global.github is set by setupGlobals() from github-script context | ||
| const github = githubClient || global.github; | ||
| const response = await github.rest.actions.getWorkflowRun({ | ||
| owner: ctx.repo.owner, | ||
| repo: ctx.repo.repo, | ||
| run_id: ctx.runId, | ||
| }); | ||
| const runCreatedAt = response.data.created_at || ""; | ||
| core.setOutput("run_created_at", runCreatedAt); | ||
| } catch (err) { | ||
| core.warning(`Unable to load workflow-run creation time: ${getErrorMessage(err)}`); | ||
| core.setOutput("run_created_at", ""); | ||
| } | ||
| core.setOutput("run_created_at", await resolveRunCreatedAt(core, ctx, githubClient)); | ||
| } | ||
|
|
||
| const frontmatterSource = process.env.GH_AW_INFO_FRONTMATTER_SOURCE || ""; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // @ts-check | ||
|
|
||
| /** | ||
| * Normalize a workflow-run creation time into the strict UTC ISO-8601 form | ||
| * (`YYYY-MM-DDTHH:MM:SSZ`) expected by the operational-value grader contract. | ||
| * | ||
| * GitHub returns `created_at` as an ISO timestamp, but callers may also receive | ||
| * an empty string (unresolved workflow output) or an offset-bearing timestamp. | ||
| * Values that cannot be parsed are reported as an empty string so callers can | ||
| * treat the timestamp as unavailable rather than propagating an invalid value. | ||
| * | ||
| * @param {unknown} value | ||
| * @returns {string} normalized timestamp, or "" when the value is unusable | ||
| */ | ||
| function normalizeRunCreatedAt(value) { | ||
| if (typeof value !== "string") return ""; | ||
| const trimmed = value.trim(); | ||
| if (trimmed === "") return ""; | ||
| const parsed = Date.parse(trimmed); | ||
| if (!Number.isFinite(parsed)) return ""; | ||
| return new Date(parsed).toISOString().replace(/\.\d{3}Z$/, "Z"); | ||
| } | ||
|
|
||
| module.exports = { normalizeRunCreatedAt }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // @ts-check | ||
|
|
||
| const { normalizeRunCreatedAt } = require("./run_created_at.cjs"); | ||
|
|
||
| describe("normalizeRunCreatedAt", () => { | ||
| it("keeps an already normalized UTC timestamp", () => { | ||
| expect(normalizeRunCreatedAt("2026-09-12T20:58:00Z")).toBe("2026-09-12T20:58:00Z"); | ||
| }); | ||
|
|
||
| it("normalizes offsets and millisecond precision", () => { | ||
| expect(normalizeRunCreatedAt("2026-09-12T22:58:00+02:00")).toBe("2026-09-12T20:58:00Z"); | ||
| expect(normalizeRunCreatedAt("2026-09-12T20:58:00.553Z")).toBe("2026-09-12T20:58:00Z"); | ||
| }); | ||
|
|
||
| it("reports unusable values as empty", () => { | ||
| expect(normalizeRunCreatedAt("")).toBe(""); | ||
| expect(normalizeRunCreatedAt(" ")).toBe(""); | ||
| expect(normalizeRunCreatedAt("not-a-timestamp")).toBe(""); | ||
| expect(normalizeRunCreatedAt(null)).toBe(""); | ||
| expect(normalizeRunCreatedAt(undefined)).toBe(""); | ||
| expect(normalizeRunCreatedAt(1757710680000)).toBe(""); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot move run create at logic into own file, add tests
Move constants to constants
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All created-at logic now lives in
run_created_at.cjs(normalizeRunCreatedAt,fetchRunCreatedAt,resolveRunCreatedAtForInfo,resolveRunCreatedAtForGrading);generate_aw_info.cjsandtrace_graders.cjsjust call it. Retry constants moved toconstants.cjs, and the tests moved intorun_created_at.test.cjs. c25ee41