-
Notifications
You must be signed in to change notification settings - Fork 414
Add structured diagnostics to the daily workflow ET guardrail #36164
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 all commits
a8e35e1
444d873
5aeda1f
c47ca96
9f4fccc
7e37cb8
95f8426
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,35 @@ async function getArtifactClient() { | |
| return new DefaultArtifactClient(); | ||
| } | ||
|
|
||
| /** | ||
| * @param {string} message | ||
| * @param {Record<string, unknown>} [details] | ||
| * @returns {string} | ||
| */ | ||
| function formatDailyGuardrailLogMessage(message, details) { | ||
| if (!details || Object.keys(details).length === 0) { | ||
| return `[daily-workflow-et] ${message}`; | ||
| } | ||
| let serializedDetails = ""; | ||
| try { | ||
| serializedDetails = JSON.stringify(details); | ||
| } catch { | ||
| serializedDetails = JSON.stringify({ error: "failed to serialize log details" }); | ||
| } | ||
| return `[daily-workflow-et] ${message}: ${serializedDetails}`; | ||
| } | ||
|
|
||
| /** | ||
| * Emit a consistently prefixed daily workflow ET diagnostic log line. | ||
| * | ||
| * @param {string} message | ||
| * @param {Record<string, unknown>} [details] | ||
| * @returns {void} | ||
| */ | ||
| function logDailyGuardrail(message, details) { | ||
| core.info(formatDailyGuardrailLogMessage(message, details)); | ||
| } | ||
|
|
||
| /** | ||
| * @returns {boolean} | ||
| */ | ||
|
|
@@ -66,12 +95,34 @@ async function getRunEffectiveTokens(artifactClient, runId, token, owner, repo) | |
| repositoryName: repo, | ||
| }, | ||
| }); | ||
| const artifactSummaries = artifacts.map(item => ({ id: item?.id ?? null, name: item?.name || "" })); | ||
| logDailyGuardrail("Listed workflow artifacts", { | ||
| runId, | ||
| artifactCount: artifacts.length, | ||
| artifacts: artifactSummaries, | ||
| }); | ||
|
Comment on lines
+98
to
+103
|
||
|
|
||
| const artifact = artifacts.find(item => matchesGuardrailArtifactName(item.name)); | ||
| const artifact = artifacts.find(item => item?.name && matchesGuardrailArtifactName(item.name)); | ||
|
Contributor
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. [/tdd] Changing the predicate from 💡 Suggested testAdd a case where |
||
| if (!artifact) { | ||
| logDailyGuardrail("No matching guardrail artifact found", { | ||
| runId, | ||
| availableArtifacts: artifactSummaries, | ||
| }); | ||
| return 0; | ||
| } | ||
| if (!artifact.id) { | ||
| logDailyGuardrail("Skipping guardrail artifact without an id", { | ||
| runId, | ||
|
Contributor
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. [/tdd] The new 💡 Suggested testAdd a unit test that stubs This pins both the guard and the new structured log emitted on that path. |
||
| artifactName: artifact.name, | ||
| }); | ||
| return 0; | ||
| } | ||
|
|
||
| logDailyGuardrail("Selected guardrail artifact", { | ||
| runId, | ||
| artifactId: artifact.id, | ||
| artifactName: artifact.name, | ||
| }); | ||
| const downloadRoot = fs.mkdtempSync(path.join(os.tmpdir(), `gh-aw-daily-guardrail-${runId}-`)); | ||
| const download = await artifactClient.downloadArtifact(artifact.id, { | ||
| path: downloadRoot, | ||
|
|
@@ -84,7 +135,20 @@ async function getRunEffectiveTokens(artifactClient, runId, token, owner, repo) | |
| }); | ||
|
|
||
| const tokenUsageFile = findTokenUsageFile(download.downloadPath || downloadRoot); | ||
| return sumEffectiveTokensFromTokenUsageFile(tokenUsageFile); | ||
| logDailyGuardrail("Downloaded guardrail artifact", { | ||
| runId, | ||
| artifactId: artifact.id, | ||
| artifactName: artifact.name, | ||
| downloadPath: download.downloadPath || downloadRoot, | ||
| tokenUsageFile, | ||
| }); | ||
| const effectiveTokens = sumEffectiveTokensFromTokenUsageFile(tokenUsageFile); | ||
| logDailyGuardrail("Computed run ET from artifact", { | ||
| runId, | ||
| artifactId: artifact.id, | ||
| effectiveTokens, | ||
| }); | ||
| return effectiveTokens; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -251,6 +315,17 @@ async function main() { | |
| return; | ||
| } | ||
|
|
||
| logDailyGuardrail("Resolved current workflow ET guardrail context", { | ||
| owner, | ||
| repo, | ||
| currentRunId: context.runId, | ||
| workflowId: currentRun.data.workflow_id, | ||
| workflowName, | ||
| actorLogin, | ||
| threshold, | ||
| rateLimitRemaining: rateLimit.remaining, | ||
| rateLimitLimit: rateLimit.limit, | ||
| }); | ||
| const maxInspectableRuns = computeMaxInspectableRuns(rateLimit.remaining); | ||
| if (maxInspectableRuns <= 0) { | ||
| core.warning(`Skipping daily workflow ET guardrail because the GitHub API rate limit is too low (${rateLimit.remaining} remaining, reserve ${RATE_LIMIT_RESERVE}).`); | ||
|
|
@@ -265,6 +340,13 @@ async function main() { | |
| let page = 1; | ||
| let truncatedByRateLimit = false; | ||
| while (page <= MAX_WORKFLOW_RUN_PAGES) { | ||
| logDailyGuardrail("Querying completed workflow runs", { | ||
| workflowId: currentRun.data.workflow_id, | ||
| actorLogin, | ||
| page, | ||
| perPage: 100, | ||
| cutoff: new Date(cutoffMs).toISOString(), | ||
| }); | ||
| const response = await githubClient.rest.actions.listWorkflowRuns({ | ||
| owner, | ||
| repo, | ||
|
|
@@ -275,6 +357,11 @@ async function main() { | |
| page, | ||
| }); | ||
| runs = response.data.workflow_runs || []; | ||
| logDailyGuardrail("Received workflow runs page", { | ||
| page, | ||
| runCount: runs.length, | ||
| runIds: runs.map(run => run?.id).filter(Boolean), | ||
|
Contributor
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. [/zoom-out] 💡 Suggested fixReplace the full ID array with a count, matching the style used in nearby logs: logDailyGuardrail("Received workflow runs page", {
page,
runCount: runs.length,
// runIds omitted — captured in candidateRunIds after filtering
});If per-page IDs are genuinely useful, consider capping the array: |
||
| }); | ||
|
Comment on lines
+360
to
+364
|
||
| if (runs.length === 0) { | ||
| break; | ||
| } | ||
|
|
@@ -297,6 +384,12 @@ async function main() { | |
| } | ||
| page += 1; | ||
| } | ||
| logDailyGuardrail("Prepared candidate workflow runs for artifact inspection", { | ||
| candidateRunsCount: candidateRuns.length, | ||
| candidateRunIds: candidateRuns.map(run => run.id), | ||
| maxInspectableRuns, | ||
| truncatedByRateLimit, | ||
| }); | ||
|
Comment on lines
+387
to
+392
|
||
|
|
||
| const artifactClient = await getArtifactClient(); | ||
| let totalEffectiveTokens = 0; | ||
|
|
@@ -310,6 +403,11 @@ async function main() { | |
| try { | ||
| const runEffectiveTokens = await getRunEffectiveTokens(artifactClient, run.id, token, owner, repo); | ||
| if (runEffectiveTokens <= 0) { | ||
| logDailyGuardrail("Skipping run without ET usage artifact data", { | ||
| runId: run.id, | ||
| currentEffectiveTokens: totalEffectiveTokens, | ||
| threshold, | ||
| }); | ||
| continue; | ||
| } | ||
| totalEffectiveTokens += runEffectiveTokens; | ||
|
|
@@ -320,6 +418,13 @@ async function main() { | |
| conclusion: run.conclusion || "", | ||
| effective_tokens: runEffectiveTokens, | ||
| }); | ||
| logDailyGuardrail("Updated current ET state", { | ||
| runId: run.id, | ||
| runEffectiveTokens, | ||
| currentEffectiveTokens: totalEffectiveTokens, | ||
|
Contributor
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. O(n2) log output inside the per-run loop: 💡 Suggested fixLog only the most recently added run ID, not the full list on each iteration: logDailyGuardrail("Updated current ET state", {
runId: run.id,
runEffectiveTokens,
currentEffectiveTokens: totalEffectiveTokens,
threshold,
countedRunsCount: countedRuns.length, // just the count
});The full |
||
| threshold, | ||
|
Contributor
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. [/zoom-out] 💡 Suggested fixLog only the newly-added run ID and the current count rather than the full accumulated array each time: logDailyGuardrail("Updated current ET state", {
runId: run.id,
runEffectiveTokens,
currentEffectiveTokens: totalEffectiveTokens,
threshold,
countedRunCount: countedRuns.length, // count, not the full array
});The complete list is already captured in the final |
||
| countedRunIds: countedRuns.map(item => item.id), | ||
| }); | ||
| } catch (error) { | ||
| core.warning(`Failed to inspect token usage for run ${run.id}: ${getErrorMessage(error)}`); | ||
| } | ||
|
|
@@ -334,6 +439,14 @@ async function main() { | |
| inspectedRunsCount: countedRuns.length, | ||
| truncatedByRateLimit, | ||
| }; | ||
| logDailyGuardrail("Completed ET inspection window", { | ||
| candidateRunsCount: summaryMeta.candidateRunsCount, | ||
| inspectedRunsCount: summaryMeta.inspectedRunsCount, | ||
| countedRunIds: countedRuns.map(run => run.id), | ||
| currentEffectiveTokens: totalEffectiveTokens, | ||
| threshold, | ||
| exceeded: totalEffectiveTokens > threshold, | ||
| }); | ||
|
|
||
| if (totalEffectiveTokens <= threshold) { | ||
| await appendDailyEffectiveWorkflowSummary(workflowName, actorLogin, threshold, countedRuns, rateLimit, summaryMeta); | ||
|
|
@@ -355,4 +468,5 @@ module.exports = { | |
| calculateDailyEffectiveWorkflowStats, | ||
| computeMaxInspectableRuns, | ||
| renderDailyEffectiveWorkflowSummary, | ||
| formatDailyGuardrailLogMessage, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,22 @@ describe("check_daily_effective_workflow_guardrail", () => { | |
| expect(exports.computeMaxInspectableRuns(120)).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it("formats structured daily ET log messages", () => { | ||
| const message = exports.formatDailyGuardrailLogMessage("Resolved current workflow ET guardrail context", { | ||
| currentRunId: 123, | ||
| workflowId: 456, | ||
| currentEffectiveTokens: 789, | ||
| }); | ||
| const prefix = "[daily-workflow-et] Resolved current workflow ET guardrail context: "; | ||
| expect(message).toContain(prefix); | ||
| expect(JSON.parse(message.slice(prefix.length))).toEqual({ | ||
| currentRunId: 123, | ||
| workflowId: 456, | ||
| currentEffectiveTokens: 789, | ||
| }); | ||
| expect(exports.formatDailyGuardrailLogMessage("Completed ET inspection window")).toBe("[daily-workflow-et] Completed ET inspection window"); | ||
| }); | ||
|
Contributor
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. [/tdd] 💡 Suggestions
|
||
|
|
||
| it("renders a daily ET details summary with stats and prior runs", () => { | ||
| const markdown = exports.renderDailyEffectiveWorkflowSummary( | ||
| "Nightly triage", | ||
|
|
||
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.
[/improve-codebase-architecture] The
catchblock informatDailyGuardrailLogMessageswallows the original serialisation error completely, making it hard to know why serialisation failed during debugging.💡 Suggested improvement
Adding
reasonpreserves the intent of defensive logging while still surfacing enough context to diagnose circular-reference or BigInt serialisation failures.